Skip to content

Developer API

Push your own content into a Chatbot knowledge base programmatically. The Developer API is connector #2 and reuses the exact same knowledge lifecycle as every other connector — there is no separate storage or engine behind it.

  • Contract / source of truth: openapi.yaml
  • Base path: all endpoints are mounted under /api, e.g. POST /api/v1/sources

Contents

  1. Concepts
  2. Quickstart
  3. Authentication & key rotation
  4. Items: single, batch, snapshot, deletes & tombstones
  5. Runs & publishing
  6. Idempotency
  7. Rate limits
  8. Errors
  9. Webhooks & signature verification
  10. Examples — curl, JavaScript, Python, C#, GitHub Actions

Concepts

flowchart LR
A[Your system] -->|push items| B[Staging set]
B -->|start run| C[Run: diff vs last applied]
C -->|apply| D[Generation built + published]
D --> E[Widget / assistant serves it]
TermMeaning
SourceA container of type developer_api that owns a staging set, its runs and its published generations. Create one per logical content collection.
ItemA single piece of content identified by a stable externalItemId you own. Re-using the id upserts the same item.
Staging setThe full desired state of a source. You mutate it (upsert / delete / snapshot); a run reads it.
RunReads the staging set, diffs it against the last applied state, records the changes. Runs execute synchronously.
ApplyBuilds and publishes a new generation from a run. The serving layer uses it immediately.
GenerationAn immutable, published snapshot of the knowledge base that the widget and assistant read from.

The golden rule: the staging set is the whole truth. A run with complete discovery turns items you removed into tombstones (subject to the source’s grace window); a rolling upsert/batch only touches the items it names.


Quickstart

A tenant administrator first creates an API key in the admin portal (or via POST /api/v1/admin/keys). The key secret is shown once.

Terminal window
export CHATBOT_API="https://your-host/api"
export CHATBOT_KEY="sk_live_xxxxxxxxxxxxxxxx_..."
# 1. Create a source
SID=$(curl -s -X POST "$CHATBOT_API/v1/sources" \
-H "Authorization: Bearer $CHATBOT_KEY" \
-H "Content-Type: application/json" \
-d '{"name":"Help center","graceDays":7}' | jq -r .source.id)
# 2. Push an item
curl -s -X POST "$CHATBOT_API/v1/sources/$SID/items" \
-H "Authorization: Bearer $CHATBOT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"externalItemId":"faq-1","title":"Refunds","content":"Refunds take 14 days."}'
# 3. Run, then apply (publish)
RID=$(curl -s -X POST "$CHATBOT_API/v1/sources/$SID/runs" \
-H "Authorization: Bearer $CHATBOT_KEY" | jq -r .run.id)
curl -s -X POST "$CHATBOT_API/v1/sources/$SID/runs/$RID/apply" \
-H "Authorization: Bearer $CHATBOT_KEY"
# → { "status": "published", "generationId": "...", "counts": { ... } }

The pushed content now serves in the widget and assistant. Full runnable examples in five languages live in examples/.


Idempotency

Every mutating request accepts an Idempotency-Key header (any unique string — a UUID is ideal). Retries are safe:

  • Same key + same payload → the original result is replayed.
  • Same key + different payload409 idempotency_conflict.
  • Keys are retained for 24 hours.

Item upserts are also naturally idempotent on (source, externalItemId), so a retried upsert never creates a duplicate even without a key. Use the header to make batches and runs safe to retry as a unit.

Terminal window
curl -X POST "$CHATBOT_API/v1/sources/$SID/items/batch" \
-H "Authorization: Bearer $CHATBOT_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: import-2024-06-01-batch-7" \
-d '{"items":[ ... ]}'

Rate limits

Requests are limited per API key (independent of source IP), default 120 requests/minute. Exceeding it returns 429 with Retry-After: 60:

{ "error": "Too many requests. Please slow down.", "code": "rate_limited" }

Back off and retry after the indicated delay. Because the limit is per key, you can isolate noisy workloads by giving them their own key.


Errors

All errors share a stable shape:

{ "error": "human readable message", "code": "machine_code" }
StatuscodeMeaning
400validation_errorThe body failed validation.
400invalid_json / invalid_bodyBody was not valid JSON.
401unauthorizedMissing/malformed Authorization header.
401invalid_key / key_expired / key_revokedThe key is not usable.
403insufficient_scopeThe key lacks the scope the endpoint requires.
404not_foundSource / run not found (or not a developer_api source).
409idempotency_conflictIdempotency-Key reused with a different payload.
409idempotency_in_progressA request with this key is still running.
409source_inactiveThe source is archived.
409not_apply_eligibleRun discovery was incomplete — cannot apply.
409apply_conflict / stale_plan / stale_base / lease_lost / apply_in_progressTransient — retry the apply.
413payload_too_largeBody exceeded the size limit (1 MiB single, 8 MiB batch).
415unsupported_media_typeContent-Type was not application/json.
429rate_limitedPer-key rate limit exceeded.
502build_failedThe knowledge build failed; the previous generation keeps serving.