Reference

API Reference

Base URL: https://api.waslo.io — every request carries your key as a Bearer token. All endpoints return JSON.

Authentication

Send your key in the Authorization header. Keys are created in the Keys page and shown once.

Authorization: Bearer wsk_live_...

POST /v1/agent/reply — the core call

Send a user message; receive the AI reply with classification, citations and usage. The server owns conversation history per userId — you never resend it.

POST /v1/agent/reply
{
  "message": "What do you think of this photo? And the price of a similar item?",
  "userId": "customer_8842",
  "tools": ["catalog", "calendar"],
  "media": [{ "type": "image", "url": "https://..." }],
  "responseFormat": "voice"
}

Request fields

messagestring

The end-user's message (1–4000 chars). Required.

userIdstring

Your stable id for the end-user (memory + history key). Required.

userNamestring?

Optional display name for the user.

systemPromptstring?

Per-request prompt override. With promptMode 'append' (default) it layers on your saved config; 'replace' swaps it.

toolsstring[]?

Enable tool families: catalog (product search + photos) and/or calendar (real bookings). Only runs if connected on your org.

mediaobject[]?

One media item: type + url (https, fetched server-side) or type + base64 + mimeType. Max 10MB.

responseFormat'text'|'voice'

'voice' returns audioUrl (hosted MP3, ~5 min TTL) alongside the text reply.

classifyboolean?

Default true — awaits classification and includes it. Set false to skip for lower latency.

Response

{
  "reply": "That's a leather bag, our price is 450 SAR.",
  "classification": "HOT",
  "citations": [{ "documentName": "catalog.pdf", "preview": "..." }],
  "products": [{ "imageUrl": "https://...", "caption": "..." }],
  "audioUrl": "https://api.waslo.io/media/voice/...",
  "usage": { "credits_charged": 5, "credits_remaining": 4995 },
  "request_id": "req_853d4e76"
}

POST /v1/agent/classify

Classification without generating a reply — 1 credit. Uses the user's stored history for context.

POST /v1/agent/classify
{ "message": "I want to book today and I'm ready to pay.", "userId": "customer_8842" }

→ { "classification": "HOT", "reason": "...", "usage": {...} }

Memory

GET /v1/memory/:userId returns the structured facts the agent holds (name, language, preferences…). DELETE erases them (GDPR).

GET/v1/memory/:userId
DELETE/v1/memory/:userId

Knowledge base

POST /v1/kb/documents (multipart, field 'file') ingests a document; GET lists with status; DELETE removes. Replies cite your docs automatically.

curl https://api.waslo.io/v1/kb/documents \
  -H "Authorization: Bearer wsk_live_..." \
  -F "file=@prices.pdf" -F "name=Prices"

Catalog

Manage your product catalog programmatically. GET lists products; POST creates; PATCH/DELETE update or remove by id. Products are shared with the dashboard catalog and searched by tools:["catalog"].

POST /v1/catalog
{ "name": "Whitening package", "price": "799 SAR", "category": "Offers" }

GET    /v1/catalog          → { "products": [...] }
PATCH  /v1/catalog/:id      → update fields
DELETE /v1/catalog/:id      → remove

Calendar

Connect a calendar so tools:["calendar"] can check availability and book appointments. Cal.com connects server-to-server with an API key (validated before saving). Google returns a one-time consent link — the calendar owner opens it once to approve, then bookings flow automatically. Works for white-label tenants too. Requires the config scope.

GET  /v1/integrations/calendar          → { "provider": "google|calcom|none", ... }

# Cal.com — server-to-server (validated against Cal.com before saving)
POST /v1/integrations/calendar/cal-com
{ "apiKey": "cal_live_...", "eventTypeId": 5784557, "eventTypeSlug": "intro-call" }

# Google — returns a one-time consent link; the calendar owner opens it once
POST /v1/integrations/calendar/google/connect-link
{ "returnUrl": "https://your-app.com/done" }
→ { "url": "https://accounts.google.com/o/oauth2/..." }

DELETE /v1/integrations/calendar/google   → disconnect
DELETE /v1/integrations/calendar/cal-com  → disconnect

How booking works

You never call a booking endpoint directly. Once a calendar is connected, pass tools:["calendar"] on /v1/agent/reply and the agent handles it inside the conversation — it checks availability, books the slot, and can list or cancel appointments on its own, confirming the result in the reply. Times use the timezone set on the connected calendar (e.g. Asia/Riyadh), so make sure that is correct. Give a clear date, time, and customer name.

POST /v1/agent/reply
{
  "message": "Book a cleaning on 2026-07-16 at 3:30 PM, name Ahmad",
  "userId": "customer_8842",
  "tools": ["calendar"]
}
// The agent runs get_availability → book_slot itself and confirms in "reply".
// It can also list_upcoming_appointments and cancel_appointment on request.

If a booking fails

The agent replies with a polite "technical issue" message instead of crashing, and usage still shows a tool reply. To diagnose, call GET /v1/integrations/calendar. The most common cause is an expired or revoked provider key — reconnect Cal.com with a fresh key created with no expiry, or re-run the Google consent link. Always confirm the provider is connected before telling customers they can book.

Config

GET/PUT /v1/config — persist your default systemPrompt, promptMode and classificationCriteria so you don't send them per request.

PUT /v1/config
{
  "systemPrompt": "You are the assistant for Pearl Dental Clinic...",
  "promptMode": "append",
  "classificationCriteria": "HOT = asks to book or pay"
}

Usage

GET /v1/usage — remaining credits + this month's requests grouped by event type.

GET/v1/usage

Errors

Uniform envelope: error.code, error.message, error.request_id. Retry 429 after the Retry-After header; 402 means top up credits.

400validation_errorValidation failed (unknown/invalid fields)
401bad_api_keyMissing, invalid, revoked or expired key
402insufficient_creditsInsufficient credit balance
403scope_not_allowedKey lacks the required scope
404not_foundUnknown userId / document
413payload_too_largeBody or media over the size limit
429rate_limit_exceededRate limit exceeded — honor Retry-After

Rate limits & idempotency

Per-key limits (default 60/min, 5,000/day — higher on paid plans). Retried a timeout? Send the same Idempotency-Key header within 24h and you'll get the cached response instead of a double charge.

Idempotency-Key: order-8842-retry-1

White-label tenants

On the White-label plan, create an isolated org per client — its own key, knowledge base, config, memory and leads. All usage bills YOUR balance; meter each client with the usage endpoint and re-bill at your own price. Deactivating revokes the client's keys (data kept).

POST /v1/tenants
{ "name": "Pearl Dental Clinic" }
→ { "tenant": { "id": "…", "name": "…" }, "rawKey": "wsk_live_…" }

GET    /v1/tenants                → { "tenants": [...] }
GET    /v1/tenants/:id/usage      → per-client monthly meter
DELETE /v1/tenants/:id            → revoke the client's keys