Who this is for. The person who runs your Sonela workspace makes the key in the dashboard (section 2). Everything after that is for whoever writes the software that will call the API — hand them this page and the key, and nothing else.
1. What it is, and what it is not
The API is a third way to reach the assistant you already have, beside the widget and the hosted page. It answers from the same manuals and, where your workspace has them, the same records and lookups, unless you set the key to read only chosen manuals, lookups and templates (Integration › API keys › Readings), and it is read-only in the same way: no request can change your data.
- A key decides everything a request may not. Which data source it asks through, whose records it asks about and what language it answers in are fixed on the key. A request carries a message and nothing that could widen any of them.
- We keep no conversation. The words of a question and of an answer are not stored. A conversation is handed back to you as a sealed value, and you send it with the next message (section 4).
- It is for a server. The API answers no browser: it sends no cross-origin allowance, so a page cannot call it, and a key in a page would be a key given away. For a page, use the widget, which carries no secret.
- It has no pictures, no voice and no documents to send. Those belong to a person at a screen. The API takes text and answers in text and tables.
2. Make a key
In the dashboard, open Integration, then API keys, then New key. The API is part of the Growth and Scale plans.
| Field | What it decides |
|---|---|
| What it may do |
chat asks questions. knowledge.read lists and reads
manuals. knowledge.write adds, replaces and removes them.
organisations.write adds your customers (section 7) and is a key
of its own. Give a key only what the software on it needs.
|
| Data source and end-tenant | For a key that asks questions: the source it asks through and the customer whose records it asks about. One key, one customer. Software that serves several customers holds one key for each. |
| Language | The language this key's answers are written in. |
| Requests a minute, questions a day | The key's own limits (section 8). They bound what a lost key could spend, so set them near what the software really needs. |
| Expires after | Optional. A key with no expiry lives until you revoke it. |
The key is shown once. It begins sk_live_. We keep only
a hash of it, so nobody can show it to you again, us included. Put it in your
server's secret store or an environment variable — never in source control, never in
a page, never in an app a visitor installs. What a key may do and what it reaches
cannot be changed afterwards: make a new key and revoke the old one.
3. Ask a question
Send the key as a bearer token and the question as JSON. The examples read the key from
an environment variable named SONELA_API_KEY.
curl https://api.sonela.ai/api/v1/chat \
-H "Authorization: Bearer $SONELA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "message": "What are your opening hours?" }'
The answer:
{
"reply": "We are open nine to five, Monday to Friday.",
"answered": true,
"tables": [],
"answeredFromRecords": false,
"answeredFromMemory": false,
"conversation": {
"id": "0d5b1c1e-…",
"state": "CfDJ8…",
"expiresAtUtc": "2026-10-01T09:30:00Z"
},
"quota": { "limit": 5000, "remaining": 4210, "resetsAtUtc": "2026-11-01T00:00:00Z" },
"receivedAtUtc": "2026-09-30T09:30:00Z"
}
| Field | Meaning |
|---|---|
reply |
The answer, as text. It may carry Markdown. |
answered |
false when the question was turned away before the assistant saw
it, and reply is the sentence saying so. Such a turn spends none of
your plan's credits and does not extend the conversation.
|
notFound |
true when reply says nothing was found on the
question, so you can offer the next step yourself; absent otherwise. The question
was answered and spent all the same.
|
tables |
Rows the answer rests on, when it read your records. |
answeredFromRecords |
Whether the answer read your records or a lookup to get there. |
answeredFromMemory |
Whether an answer already given to the same question was served again. Answers that rest on records are never served this way. |
conversation |
What to send back to carry on (section 4). |
quota |
What is left of your plan's question credits, and when they reset. |
4. Carry on a conversation
To ask a follow-up, send the state the last answer gave you. Leave it out
to start again.
{
"message": "And on Saturdays?",
"conversation": "CfDJ8…"
}
- Treat the state as the conversation itself. It holds the conversation's recent words, sealed so that only we can open it and only under the key's own data source and end-tenant. Store it where you would store the conversation, and with the same care.
- It changes with every answer. Always send the newest one.
-
It expires. A state is good for 24 hours from the answer that
carried it;
expiresAtUtcsays when. -
A state that does not open is an ordinary outcome. The request
answers
400 conversation_invalid. Send the message again without the state, and the conversation starts afresh.
5. Stream the answer
Ask for a stream with one header and the same request answers as server-sent events, so your software can show the answer as it is written.
curl -N https://api.sonela.ai/api/v1/chat \
-H "Authorization: Bearer $SONELA_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{ "message": "What are your opening hours?" }'
| Event | Data | What to do with it |
|---|---|---|
status |
{ "code": "thinking" | "records" | "writing" } |
Show progress, or ignore it. |
table |
One table of rows | Show it, or wait for the final answer, which carries it again. |
text |
{ "text": "…" } |
Append it to what you have shown. |
reset |
{} |
Discard everything shown so far; the answer is starting again. |
heartbeat |
{} |
Nothing. It keeps the connection open. |
final |
The whole answer, exactly as section 3 shows it | Replace what you have shown with it. This is the answer. |
error |
A refusal, as section 9 describes them | The answer failed after the stream began. |
6. Keep your manuals up to date
A key with knowledge.read or knowledge.write reaches the
manuals the assistant answers from — the same ones the Manuals screen shows.
| Request | Scope | What it does |
|---|---|---|
GET /api/v1/documents |
knowledge.read |
Lists the manuals, how many you may hold, and how they reach the assistant. |
GET /api/v1/documents/{id} |
knowledge.read |
One manual, with its text. |
POST /api/v1/documents |
knowledge.write |
Adds a manual: { "title": "…", "content": "…", "publicAudience": true }. |
PUT /api/v1/documents/{id} |
knowledge.write |
Replaces a manual's title and text. |
DELETE /api/v1/documents/{id} |
knowledge.write |
Removes a manual. |
POST /api/v1/documents/convert |
knowledge.write |
Turns a spreadsheet, a Word document or a PDF into text and hands it back. Stores nothing. |
curl https://api.sonela.ai/api/v1/documents \
-H "Authorization: Bearer $SONELA_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "title": "Returns policy", "content": "Goods may be returned within 30 days…" }'
A file takes two requests on purpose. Send it to convert as a form upload
in a field named file, read what it became, correct anything the
conversion got wrong, and save that text as a manual. Nothing reaches the assistant
that nobody has had the chance to read.
curl https://api.sonela.ai/api/v1/documents/convert \
-H "Authorization: Bearer $SONELA_API_KEY" \
-F "file=@price-list.xlsx"
7. Add your customers
If each of your customers has a database of their own with the same tables, your sign-up
flow can add them as it creates them. Connect one customer's database in the dashboard
and approve its schema once: it becomes the template. A key with
organisations.write, which holds nothing else, then reaches these doors.
| Request | What it does |
|---|---|
POST /api/v1/organisations |
Connects the customer's database, approves it only if it reads exactly as the template does, copies the template's glossary, and sends the customer to it. |
PUT /api/v1/organisations/{id}/route |
Sends a customer to one of your databases, or moves them:
{ "dataSourceId": "…" }.
|
DELETE /api/v1/organisations/{id} |
Lets a customer go. Their assistant stops at once; their database stays until you remove it in the dashboard. |
GET /api/v1/organisations |
Your customers and the database each one reads. |
curl https://api.sonela.ai/api/v1/organisations \
-H "Authorization: Bearer $SONELA_PROVISIONING_KEY" \
-H "Content-Type: application/json" \
-d '{ "endTenantId": "acme", "name": "Acme Ltd",
"templateDataSourceId": "…",
"connectionString": "Host=…;Database=acme;Username=sonela_reader;Password=…" }'
endTenantId is the id your voucher endpoint signs for that customer, and
{id} in a path is the same id, URL-escaped. A database whose tables, columns
or comments differ from the template's is refused with
409 stamp_schema_differs or 409 stamp_notes_differ and a
differences list naming the tables; nothing is made, and you connect it in
the dashboard instead. A database that belongs to one customer cannot be given to a
second: 409 organisation_route_source_owned.
8. Limits
| Limit | Whose | When it is reached |
|---|---|---|
| Requests a minute | The key's, set by you | 429 with Retry-After, in seconds. |
| Questions a day | The key's, set by you |
429 api_key_day_spent, with limit,
resetsAtUtc and Retry-After. It resets at midnight
UTC.
|
| Your plan's question credits | The workspace's, shared with the widget and the hosted page. A question costs one, or two if it reads your records. | 429 quota_exceeded, with when the period resets. |
| Manuals written | The workspace's | 429 with Retry-After. |
On any 429, wait for as long as Retry-After says before
trying again. Software that retries at once spends the next minute's allowance on
refusals.
9. When a request is refused
A refusal is JSON with a status, a title, a
detail written for a person, and a code written for your
software. Branch on the code, never on the words.
| Status and code | What it means | What to do |
|---|---|---|
401 api_key_invalid |
The key is unknown, revoked, expired or replaced. Which one is never said. | Do not retry. Check the key's row in the dashboard. |
403 api_key_scope |
The key was made without what this request needs. | Make a key that has it. |
403 api_not_on_plan |
The workspace's plan does not include the API. | Change the plan. |
403 assistant_paused |
Your workspace has paused its assistant. Manuals can still be read and written. | Resume it under Integration, Availability. |
400 conversation_invalid |
The conversation's state did not open. | Send the message again without it. |
429 |
A limit of section 8. | Wait for Retry-After. |
503 assistant_not_ready |
Something the answer needs is not set up in the workspace. | Open the dashboard; its setup screen says what is missing. |
503 data_source_unavailable502 provider_failed
|
Your records, or the AI provider, did not answer. | Try again after a pause. |
503 api_unavailable |
The API is switched off. | Try again later. |
409 document_limit_reached400 document_too_long
|
The library is full, or a manual is longer than one may be. | Remove a manual, or split the text. |
400 document_file_missing400 document_not_converted
|
No file arrived in the file field, or it could not be read. |
The detail says which. |
404 |
No manual of yours has that id. | List the manuals again. |
10. Rotating and revoking
- Rotate gives the same key a new secret. By default the old secret stops at once. If you cannot deploy the new one at once, give the old one time to overlap — up to 24 hours — and both work until it ends.
- Revoke stops a key at its next request, and ends any old secret a rotation left working. It cannot be undone.
- If a key may have leaked, revoke it first and make a new one after. A rotation with an overlap leaves the leaked secret working for as long as the overlap lasts.
- If you find a Sonela key that is not yours, send it to hello@sonela.ai and we will end it.
11. The checklist
- The key has only the scopes the software needs.
- Its limits are near what the software really uses.
- It lives in a secret store or an environment variable, on a server.
- The software branches on
code, and waits onRetry-After. - It sends the newest conversation state, and starts again on
conversation_invalid. - It treats
401as final and does not retry it. - Somebody reads the email that says a key was made.