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
- Concepts
- Quickstart
- Authentication & key rotation
- Items: single, batch, snapshot, deletes & tombstones
- Runs & publishing
- Idempotency
- Rate limits
- Errors
- Webhooks & signature verification
- 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]| Term | Meaning |
|---|---|
| Source | A container of type developer_api that owns a staging set, its runs and its published generations. Create one per logical content collection. |
| Item | A single piece of content identified by a stable externalItemId you own. Re-using the id upserts the same item. |
| Staging set | The full desired state of a source. You mutate it (upsert / delete / snapshot); a run reads it. |
| Run | Reads the staging set, diffs it against the last applied state, records the changes. Runs execute synchronously. |
| Apply | Builds and publishes a new generation from a run. The serving layer uses it immediately. |
| Generation | An 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.
export CHATBOT_API="https://your-host/api"export CHATBOT_KEY="sk_live_xxxxxxxxxxxxxxxx_..."
# 1. Create a sourceSID=$(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 itemcurl -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 payload →
409 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.
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" }| Status | code | Meaning |
|---|---|---|
| 400 | validation_error | The body failed validation. |
| 400 | invalid_json / invalid_body | Body was not valid JSON. |
| 401 | unauthorized | Missing/malformed Authorization header. |
| 401 | invalid_key / key_expired / key_revoked | The key is not usable. |
| 403 | insufficient_scope | The key lacks the scope the endpoint requires. |
| 404 | not_found | Source / run not found (or not a developer_api source). |
| 409 | idempotency_conflict | Idempotency-Key reused with a different payload. |
| 409 | idempotency_in_progress | A request with this key is still running. |
| 409 | source_inactive | The source is archived. |
| 409 | not_apply_eligible | Run discovery was incomplete — cannot apply. |
| 409 | apply_conflict / stale_plan / stale_base / lease_lost / apply_in_progress | Transient — retry the apply. |
| 413 | payload_too_large | Body exceeded the size limit (1 MiB single, 8 MiB batch). |
| 415 | unsupported_media_type | Content-Type was not application/json. |
| 429 | rate_limited | Per-key rate limit exceeded. |
| 502 | build_failed | The knowledge build failed; the previous generation keeps serving. |