Deskbell

HTTP API

Every public endpoint on a Deskbell instance, with request and response fields rendered from the same schemas the server validates with, the guard in front of each, and the event format of the answer stream.

The public API lives under /api/v1 on your instance. It is what the widget and the SDK call, it reads no cookies, and every route accepts OPTIONS for preflight. Bodies are JSON. The field tables on this page are generated from the schemas the server validates with; the same contract is available as an OpenAPI document for client generators.

Guards

Every request passes the same checks in order before the handler runs. Security explains each; the codes are:

CodeCauseBody
403The User-Agent looks like a crawler or an HTTP library (curl/, python-requests, axios/, HeadlessChrome…){"error": …}
404Unknown project key{"error": …}
403The request's Origin is not on the project's allowlist{"error": "Origin not allowed", "reason": …}
429A per-IP or per-project rate limit, the daily token budget, or the per-IP stream cap{"error": "Rate limited", "retryAfter": seconds}, with Retry-After
401The project requires a visitor token and none was sent, or it failed verification{"error": "Unauthorized", "reason": "missing" | "invalid"}

POST routes require an Origin header. A signed-in visitor sends Authorization: Bearer <token> from the project's identity provider; Visitor identity says what is verified.

Ask

The one call that spends model tokens. Runs every guard, plus the daily token budget and the per-address stream cap.

POST /api/v1/ask

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringAn identifier the client generated and keeps (the widget stores it in localStorage). A conversation belongs to the session that created it. 1 to 128 chars.
conversationId optionalstringContinue this conversation. Omit to start a new one. Must belong to the caller, or the response is 404.
messagestringThe visitor's question, up to 4000 characters. up to 4000 chars.
locale optionalstringBCP 47 tag of the visitor's UI language. Retrieval is cross-language. up to 35 chars.
context optionalobjectWhere on your site the conversation starts, as identifiers keyed by name. Stored on a new conversation only and ignored when continuing one.
approval optionalobjectThe visitor's answer to a confirm event: the call id and whether to go ahead. Continues the turn that paused in conversationId; message may be empty. A call that is not pending there, or has expired, is 404.

Response 200 text/event-stream

An event stream. Each `data:` line is one JSON event.

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

The response is text/event-stream. Each event is one data: line holding a JSON object; lines beginning with : are heartbeat comments and carry nothing. The stream opens with a comment at once so the client knows it is connected, and sends another after every quiet interval so a proxy does not close it mid-answer.

EventFieldsWhen
tokenvalueOnce per streamed piece of the answer
citationscitations[]: documentId, title, snippet, url?, kind?After the answer, only the sources the model used
refusalreason: no_context, no_grounding, off_topic, guardrailInstead of citations, when there is no grounded answer
clarifyquestion, options[] (page titles)Instead of an answer, when the pages answer the question in more than one way and DESKBELL_CLARIFY=on; send an option back as the next message. See Clarifying questions
actionid, title, status: running, succeeded, failedA tool call on the visitor's behalf started or ended (Actions)
confirmid, title, summary, details, input, expiresAtThe turn paused on a tool that needs the visitor's confirmation; details is the argument list the card shows (label, value pairs); done follows at once
doneconversationId, messageIdLast. messageId is what feedback refers to

kind names the source type of a citation (docs-crawler, corrections for a written answer, action for a tool result). A citation without url is a written answer or a tool result.

To answer a confirm event, call /api/v1/ask again with the same conversationId, an empty message and approval: { callId, approved }. The rest of the answer streams as usual. See Confirmation.

Config

The project's widget appearance as saved on the Appearance page, plus poweredBy, which the server decides. Cached for 60 seconds (Cache-Control: public, max-age=60, stale-while-revalidate=300).

GET /api/v1/config

Query

FieldTypeDescription
keystringThe project's public key, as printed in the embed snippet. Public by design.

Response 200 application/json

FieldTypeDescription
poweredBybooleanWhether the Powered by Deskbell badge renders.

Errors

  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Conversations

All five take projectKey, sessionId and, where named, conversationId, and answer 404 for a conversation the caller does not own.

List

POST /api/v1/conversations

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringAn identifier the client generated and keeps (the widget stores it in localStorage). A conversation belongs to the session that created it. 1 to 128 chars.
scope optionalobjectKeep only conversations whose stored context carries this key with this id.
limit optionalintegerHow many to return, 1 to 50. Default 20. 1 to 50.

Response 200 application/json

FieldTypeDescription
conversationsobject[]

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Messages

POST /api/v1/conversations/messages

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringAn identifier the client generated and keeps (the widget stores it in localStorage). A conversation belongs to the session that created it. 1 to 128 chars.
conversationIdstringThe conversation to act on. Must belong to the caller, or the response is 404.

Response 200 application/json

FieldTypeDescription
messagesobject[]

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Rename

POST /api/v1/conversations/rename

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringAn identifier the client generated and keeps (the widget stores it in localStorage). A conversation belongs to the session that created it. 1 to 128 chars.
conversationIdstringThe conversation to act on. Must belong to the caller, or the response is 404.
titlestringThe new title, 1 to 120 characters, trimmed. 1 to 120 chars.

Response 200 application/json

FieldTypeDescription
oktrue
titlestring

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Delete

POST /api/v1/conversations/delete

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringAn identifier the client generated and keeps (the widget stores it in localStorage). A conversation belongs to the session that created it. 1 to 128 chars.
conversationIdstringThe conversation to act on. Must belong to the caller, or the response is 404.

Response 200 application/json

FieldTypeDescription
oktrue

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Preview

For support staff: any conversation of the project, for a bearer token that carries the project's configured staff claim. See Staff preview.

POST /api/v1/conversations/preview

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringAn identifier the client generated and keeps (the widget stores it in localStorage). A conversation belongs to the session that created it. 1 to 128 chars.
conversationIdstringAny conversation of the project. Requires a bearer token carrying the project's staff claim.

Response 200 application/json

FieldTypeDescription
conversationobject
endUserobject | nullThe signed-in visitor the conversation belongs to, when it has one.
messagesobject[]

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Feedback

Upserts the vote on one assistant message. A down vote is what notifications fire on and what the Gaps page counts.

POST /api/v1/feedback

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.
sessionIdstringMust match the conversation's session: possession is the authorization. 1 to 128 chars.
messageIdstringThe assistant message being rated, from the ask stream's done event.
feedback"up" | "down" | nullThe vote. null clears a previous one.
reason optionalstring | nullA downvote reason id: one the project configured, or the built-in incorrect, irrelevant or unaddressed. Kept only with down.
comment optionalstring | nullFree text with a downvote, up to 1000 characters. Kept only with down. up to 1000 chars.

Response 200 application/json

FieldTypeDescription
oktrue

Errors

  • 400 The body failed validation.
  • 401 The project requires a visitor token and none was sent, or it failed verification. reason is missing or invalid.
  • 403 The User-Agent looks like a crawler or an HTTP library, or the Origin is not on the project's allowlist.
  • 404 Unknown project key, or a conversation the caller does not own.
  • 429 A rate limit, the project's daily token budget, or the per-address stream cap. Retry-After says when.

Ping

The install beacon. Diagnostic only: it records where the widget is live, including hosts not on the allowlist, and grants nothing.

POST /api/v1/ping

Request body

FieldTypeDescription
projectKeystringThe project's public key, as printed in the embed snippet. Public by design.

Response 204

Recorded.

Errors

  • 400 The body failed validation.
  • 403 The User-Agent looks like a crawler or an HTTP library.
  • 404 Unknown project key.
  • 429 Rate limited.

Other routes

RouteWhat it is
GET /deskbell.jsThe widget loader, built into the image. 503 until the widget has been built
GET /api/health{"ok": true, "service": "deskbell-web"}. What the Compose health check and a load balancer poll
GET /gaps/export?format=csv|datasetA dashboard download, session-gated. The dataset format loads into the eval harness
GET /docs/openapi.jsonThis contract as OpenAPI 3.1, served by the documentation site

The dashboard's own pages and /api/auth/* belong to the operator side and use cookie sessions; they are not part of this API.

On this page