Assistants
Answer questions from your documents
Upload your own content, attach it to an assistant, tell the assistant to answer from it, and confirm it did by reading the call transcript.
By the end of this you will have an assistant that answers callers from documents you wrote (opening hours, prices, the cancellation policy) instead of taking a message or improvising, and you will have read the proof in a transcript.
There is no training step and no waiting period. A document you update at 9am is what the assistant answers from on the next call.
Overview#
Three resources, and the distinction between the first two is the thing to get straight before you write any code.
| Resource | What it is |
|---|---|
| Document | The content itself. It lives once, under /documents, and is the only place the text is stored. |
| Attachment | The link between one document and one assistant, under /assistants/{id}/documents. It carries an enabled flag and no text of its own. |
| Prompt | The standing instructions that tell the assistant to consult its documents rather than guess. |
Because the text lives in one place and the links live in many, editing a document changes every assistant attached to it, while disabling one attachment changes exactly one assistant. Those are different operations, and mixing them up is the usual source of confusion here.
Prerequisites#
- A tenant API key, sent as
Authorization: ApiKey YOUR_API_KEYon every request. See Authentication. - An assistant reachable on a phone number, so you can hear the result: either from the Quickstart or from Take bookings by phone.
- Some content you already have, as plain text. Two short files beat one long one: the assistant looks things up by topic, and topics are easier to keep separate than to untangle.
Create the documents#
POST /documents takes either a JSON body with the text inline, or a multipart/form-data file upload. Create one document per topic.
curl -X POST https://core-api.heysadie.ai/documents \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Opening hours",
"filename": "opening-hours.txt",
"content": "Riverside Bistro is open midday to 10pm, Tuesday to Sunday. We are closed on Mondays and on public holidays. The kitchen stops taking orders at 9:15pm."
}'Or upload a file you already keep:
curl -X POST https://core-api.heysadie.ai/documents \
-H "Authorization: ApiKey YOUR_API_KEY" \
-F "name=Cancellation policy" \
-F "[email protected]"The response is the document record:
{
"success": true,
"data": {
"id": "0195f1e2-0000-0000-0000-000000000000",
"name": "Opening hours",
"sizeBytes": 172,
"createdAt": "2026-08-17T09:12:04.000Z",
"updatedAt": "2026-08-17T09:12:04.000Z",
"usedByCount": 0,
"usedByAssistantIds": []
},
"metadata": {
"timestamp": "2026-08-17T09:12:04.000Z",
"version": "1.0",
"path": "/documents"
}
}Keep data.id for each document. usedByCount is 0 and usedByAssistantIds is empty because the document exists but nothing uses it yet; that is what the next step fixes.
Write the content the way you would answer the question out loud. Short declarative sentences, one fact each, no tables or column layouts: the assistant reads this text and speaks from it, so anything that only makes sense visually is wasted.
These limits are enforced, just not in the spec
The OpenAPI document declares no request body for POST /documents, so the fields above
cannot be read from it. They are enforced on the server: name 1 to 200 characters,
filename 1 to 255 and must end in .txt, content up to 1 MB. All three are required. Both
content types the spec describes, JSON and multipart, take the same fields. The
Knowledge base guide has the full treatment.
Attach them to the assistant#
Attaching takes a single field, because the attachment carries no content: the document exists, the assistant exists, and this creates the link between them. Repeat it once per document.
curl -X POST https://core-api.heysadie.ai/assistants/ASSISTANT_ID/documents \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "documentId": "0195f1e2-0000-0000-0000-000000000000" }'Then read the attachments back, rather than assuming what you just created:
curl https://core-api.heysadie.ai/assistants/ASSISTANT_ID/documents \
-H "Authorization: ApiKey YOUR_API_KEY"{
"success": true,
"data": {
"documents": [
{
"id": "0195f1e2-0000-0000-0000-000000000000",
"name": "Opening hours",
"sizeBytes": 172,
"attachedAt": "2026-08-17T09:31:47.000Z",
"enabled": true
}
]
},
"metadata": {
"timestamp": "2026-08-17T09:40:02.000Z",
"version": "1.0",
"path": "/assistants/ASSISTANT_ID/documents"
}
}enabled is the field to check: a disabled attachment is simply not used on that assistant's calls, and it is the reversible way to take a seasonal menu or an expired promotion out of service without detaching anything.
Attach response
POST /assistants/{id}/documents returns 201 with an empty body, and a new attachment starts with enabled: true. Check response.ok rather than parsing JSON; use the list endpoint when you want to confirm the stored state.
Tell the assistant to answer from them#
Attached documents turn lookup on. Without the Knowledge Base tool on the assistant, that is the original lookup. Link the tool to narrow when it runs. Control when the assistant looks up covers the per-assistant sentence and the tenant-wide default.
The prompt still decides what to do with the answer, and what to do when the answer is not there. That second half is what stops an assistant inventing a price.
curl -X POST https://core-api.heysadie.ai/prompts \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Bistro front desk",
"type": "voice",
"prompt": "You answer the phone for Riverside Bistro. Be warm and brief. Answer questions about hours, prices, and our policies from the documents attached to you, and quote them exactly rather than paraphrasing a number or a date. If the answer is not in your documents, say you will check and offer to take a message or put the caller through. Never guess, and never state a price, a date, or a policy you cannot find."
}'type: "voice" is the default and is the type that fills the assistant's promptId slot. The response carries the prompt's id, name, type and timestamps.
Now point the assistant at it:
curl -X PATCH https://core-api.heysadie.ai/assistants/ASSISTANT_ID \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "promptId": "PROMPT_ID" }'Prompt or developer prompt?
An assistant with no promptId uses the platform's default prompt, so write yours as a complete set of standing instructions (persona, boundaries, and the rule about answering from documents) rather than a single added line. If one added line is all you want, developerPrompt is documented as a section injected into the default prompt, and it takes {{placeholders}} filled from parameters. A prompt created through /prompts is reusable across assistants; a developerPrompt belongs to one.
Ask it something only the documents know#
Dial the assistant's number and ask a question whose answer appears nowhere except your document: "how late does the kitchen take orders?" rather than "are you open on Sundays?", because the second is guessable and the first is not. A correct, specific answer is the whole test. Then hang up.
Read the transcript back:
curl "https://core-api.heysadie.ai/calls?assistantId=ASSISTANT_ID&limit=1" \
-H "Authorization: ApiKey YOUR_API_KEY"data.calls[0].transcript is the conversation as text, and summary is the platform's summary of it. Both are produced after the call ends, so if they are still null, wait a moment and run the request again. GET /calls/{id} returns the same call with its full payload, including messages turn by turn. See Calls.
Read the assistant's answer against your document, word for word. If the wording came from your text, the knowledge base is working. If the assistant hedged or improvised, check these in order:
- Is the attachment
enabled?GET /assistants/{id}/documentsfrom step 2 answers it. - Does the document actually contain the answer?
GET /documents/{id}/contentreturns the stored text; a truncated or reformatted upload is a common surprise. - Do the instructions tell it to look? A prompt that never mentions the documents leaves the model to decide, and it will sometimes decide not to. If it looks the knowledge base up for questions you already handle with another tool, set when-to-call text. See Control when the assistant looks up.
- Was the question answerable at all? If the fact is genuinely absent, an assistant that offers to take a message is behaving exactly as instructed. Pair the knowledge base with a transfer reason so those callers reach a person.
More checks, including prompt and tool behaviour, are in Calls and assistant behavior.
What you have#
An assistant that answers from your own material, and a document library any other assistant can share. Two habits make it hold up:
- Edit the document, never the prompt, when a fact changes. The
iddoes not change on an edit, so there is nothing to re-attach, and every assistant attached to that document gets the new answer at once. - Keep one topic per document. It makes a stale fact easy to find, and it lets you disable a topic on one assistant without taking the rest of your content with it.
Next steps#
- Knowledge base: all ten endpoints, including how to disable an attachment and why a deletion is blocked while one exists.
- Control when the assistant looks up: narrow lookup so it does not fire on every assistant-specific question.
- Prompts: reusable prompts, global templates, and the chat and whisper-summary slots.
- Take bookings by phone: capture forms and hand-offs, on the same assistant.
- Calls: filters, statuses and the full call payload behind the transcript you just read.