Assistants
Manage documents
Upload a document once to your library, attach it to as many assistants as need it, and control each attachment independently.
A document lives in one place and gets used in many. You upload it once to your library, then attach it to whichever assistants should answer from it. Each of those attachments can be switched off without touching the document or the other assistants.
That split runs through all ten endpoints, so it is worth getting straight before you write any code. Every parameter and response field is also listed in the Documents API reference.
Two resources, one document#
The document is the content: a name, some text, a size, and timestamps. It exists once, under /documents, and it is the only place the text lives.
The attachment is the link between one document and one assistant. It lives under /assistants/{id}/documents, carries its own enabled flag, and holds no text of its own.
┌── attachment ──► Bookings assistant (enabled)
"Opening hours" ───┼── attachment ──► Support assistant (enabled)
one document └── attachment ──► Out-of-hours line (disabled)Editing the text changes what every attached assistant answers from. Disabling an attachment changes one assistant and leaves the rest alone. Detaching removes the link and the document survives. Those are three different operations on two different resources, and mixing them up is the usual source of confusion here.
Library endpoints#
| Method | Path | Purpose |
|---|---|---|
| POST | /documents | Create a document from inline text or a file upload |
| GET | /documents | List documents, with search and filtering |
| GET | /documents/{id} | Get one document's metadata, including where it is used |
| GET | /documents/{id}/content | Read the document's stored text |
| PATCH | /documents/{id} | Update the name and/or the content |
| DELETE | /documents/{id} | Delete the document (blocked while it is attached) |
Attachment endpoints#
| Method | Path | Purpose |
|---|---|---|
| GET | /assistants/{id}/documents | List the documents attached to an assistant |
| POST | /assistants/{id}/documents | Attach an existing document |
| PATCH | /assistants/{id}/documents/{documentId} | Enable or disable one attachment |
| DELETE | /assistants/{id}/documents/{documentId} | Detach the document from the assistant |
Responses that carry a payload use the standard response envelope (success, data, metadata), and failures replace data with the error object.
The document library#
Create a document#
POST /documents accepts either a JSON body with the text inline, or a multipart/form-data file upload.
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Display name for the document, max 200 characters |
filename | string | JSON only | Must end in .txt, max 255 characters |
content | string | JSON only | The document text, up to 1 MB |
file | file | Multipart only | The plain-text file to upload |
These limits are enforced but absent from the spec
The OpenAPI document declares no request body for POST /documents or
PATCH /documents/{id}, so the constraints below cannot be read from it. They are enforced
on the server:
| Field | Constraint |
|---|---|
name | 1 to 200 characters, required |
filename | 1 to 255 characters, required, and must end in .txt (case-insensitive) |
content | 1 to 1,048,576 characters (a 1 MB ceiling), required |
The .txt requirement is a real filename check, not a convention: any other extension is
rejected with Filename must end in .txt. Both content types the spec describes (JSON and
multipart) accept the same three fields.
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": "We are open Monday to Friday, 9am to 5pm."
}'Or as a file upload:
curl -X POST https://core-api.heysadie.ai/documents \
-H "Authorization: ApiKey YOUR_API_KEY" \
-F "name=Opening hours" \
-F "[email protected]"The response carries the document object, the same shape returned by GET /documents/{id} and PATCH /documents/{id}.
{
"success": true,
"data": {
"id": "0195f1e2-0000-0000-0000-000000000000",
"name": "Opening hours",
"sizeBytes": 41,
"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"
}
}| Field | Type | Description |
|---|---|---|
id | string | The document's UUID. Use it everywhere else on this page. |
name | string | The display name you gave it. |
sizeBytes | number | Size of the stored content, in bytes. |
createdAt | string | ISO 8601 UTC. See conventions. |
updatedAt | string | ISO 8601 UTC. Moves every time you update the document. |
usedByCount | number | How many assistants the document is attached to. |
usedByAssistantIds | array | The IDs of those assistants. |
List documents#
GET /documents returns a page of documents plus a total.
| Parameter | Type | Required | Description |
|---|---|---|---|
limit | number | No | Page size. Default 50, maximum 200. |
offset | number | No | Items to skip. Default 0. |
search | string | No | Return only documents matching this text. |
usedByAssistant | string | No | Return only the documents attached to this assistant, by assistant ID. |
curl "https://core-api.heysadie.ai/documents?limit=25&search=hours" \
-H "Authorization: ApiKey YOUR_API_KEY"{
"success": true,
"data": {
"documents": [
{
"id": "0195f1e2-0000-0000-0000-000000000000",
"name": "Opening hours",
"sizeBytes": 41,
"createdAt": "2026-08-17T09:12:04.000Z",
"updatedAt": "2026-08-17T09:12:04.000Z",
"usedByCount": 2
}
],
"total": 1
},
"metadata": {
"timestamp": "2026-08-17T09:20:11.000Z",
"version": "1.0",
"path": "/documents"
}
}Two things the list does not carry. It returns usedByCount but not usedByAssistantIds; to learn which assistants use a document, fetch it individually. And it never returns the text; that is what GET /documents/{id}/content is for. Page through a long library by advancing offset until you have seen total items, as described in conventions.
Get one document#
GET /documents/{id} returns the full document object, including usedByAssistantIds.
curl https://core-api.heysadie.ai/documents/0195f1e2-0000-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY"usedByAssistantIds is the field that makes this endpoint worth calling before you change anything. It answers "which assistants would this edit affect?" and it is the exact list you have to work through before a document can be deleted.
Read a document's text#
GET /documents/{id}/content returns the document's stored text rather than a metadata record.
curl https://core-api.heysadie.ai/documents/0195f1e2-0000-0000-0000-000000000000/content \
-H "Authorization: ApiKey YOUR_API_KEY"Raw text response
This endpoint returns 200 with Content-Type: text/plain; charset=utf-8. The body is the stored document text itself, outside the usual JSON envelope, so read it with response.text().
Update a document#
PATCH /documents/{id} updates the name, the content, or both, using the same JSON or multipart body as create.
curl -X PATCH https://core-api.heysadie.ai/documents/0195f1e2-0000-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "content": "We are open every day, 9am to 9pm." }'The response is the document object again, with a new updatedAt and, if you replaced the content, a new sizeBytes. The id does not change, so every existing attachment still points at the same document; there is nothing to re-attach after an edit.
Delete a document#
DELETE /documents/{id} soft-deletes the document. The spec publishes a success response with no schema for this operation, so branch on the status code rather than on a body.
curl -X DELETE https://core-api.heysadie.ai/documents/0195f1e2-0000-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY"Detach before you delete
The request fails with 409 while the document is still attached to any assistant. Read usedByAssistantIds from GET /documents/{id}, detach the document from each assistant on that list, then delete it. If you only want an assistant to stop using the document, disabling the attachment is the smaller and reversible move.
Attaching documents to an assistant#
An attachment carries no content of its own: the assistant exists, the document exists, and the attachment links the two and records whether the link is live. That is why attaching takes a single field.
Attach a document#
POST /assistants/{id}/documents
| Field | Type | Required | Description |
|---|---|---|---|
documentId | string | Yes | UUID of the document to attach |
curl -X POST https://core-api.heysadie.ai/assistants/0195f1e2-1111-0000-0000-000000000000/documents \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "documentId": "0195f1e2-0000-0000-0000-000000000000" }'Empty success responses
- Attaching a document returns
201with an empty body. - Detaching a document returns
204with an empty body. - Deleting an unattached document returns
204with an empty body.
Check response.ok; do not call response.json() for these operations. Use GET /assistants/{id}/documents when you need the resulting attachment state.
A new attachment starts with enabled: true, appears in GET /assistants/{id}/documents, and counts toward the document's usedByCount.
List an assistant's documents#
GET /assistants/{id}/documents returns what this one assistant is attached to.
curl https://core-api.heysadie.ai/assistants/0195f1e2-1111-0000-0000-000000000000/documents \
-H "Authorization: ApiKey YOUR_API_KEY"{
"success": true,
"data": {
"documents": [
{
"id": "0195f1e2-0000-0000-0000-000000000000",
"name": "Opening hours",
"sizeBytes": 41,
"attachedAt": "2026-08-17T09:31:47.000Z",
"enabled": true
}
]
},
"metadata": {
"timestamp": "2026-08-17T09:40:02.000Z",
"version": "1.0",
"path": "/assistants/0195f1e2-1111-0000-0000-000000000000/documents"
}
}This is the attachment view, and its shape differs from the library list:
| Field | Type | Description |
|---|---|---|
id | string | The document's UUID. |
name | string | The document's display name. |
sizeBytes | number | Size of the stored content, in bytes. |
attachedAt | string | ISO 8601 UTC (when this assistant was attached to the document). |
enabled | boolean | Whether this attachment is live for this assistant. |
There is no total here and no usedByCount: this endpoint answers "what does this assistant know?", not "how widely is this document used?". For the latter, use GET /documents/{id}.
Enable or disable an attachment#
PATCH /assistants/{id}/documents/{documentId} flips one attachment on or off without removing it.
| Field | Type | Required | Description |
|---|---|---|---|
enabled | boolean | Yes | Whether the attached document is included in the assistant's knowledge base |
curl -X PATCH https://core-api.heysadie.ai/assistants/0195f1e2-1111-0000-0000-000000000000/documents/0195f1e2-0000-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "enabled": false }'The response returns the attachment itself, nested under data.document:
{
"success": true,
"data": {
"document": {
"assistantId": "0195f1e2-1111-0000-0000-000000000000",
"documentId": "0195f1e2-0000-0000-0000-000000000000",
"enabled": false,
"attachedAt": "2026-08-17T09:31:47.000Z"
}
},
"metadata": {
"timestamp": "2026-08-17T10:02:19.000Z",
"version": "1.0",
"path": "/assistants/0195f1e2-1111-0000-0000-000000000000/documents/0195f1e2-0000-0000-0000-000000000000"
}
}Both IDs come back, which makes the change easy to log: assistantId and documentId identify the attachment, enabled is its new state, and attachedAt is unchanged; toggling does not re-attach anything. The flag applies to this assistant only; every other assistant attached to the same document is unaffected.
Detach a document#
DELETE /assistants/{id}/documents/{documentId} removes the link.
curl -X DELETE https://core-api.heysadie.ai/assistants/0195f1e2-1111-0000-0000-000000000000/documents/0195f1e2-0000-0000-0000-000000000000 \
-H "Authorization: ApiKey YOUR_API_KEY"The document stays in your library and every other assistant keeps its own attachment. Detach when an assistant should stop using a document for good, or as the step that unblocks DELETE /documents/{id}. Like attaching, this operation publishes no response schema, so confirm the result with GET /assistants/{id}/documents.
Next steps#
- Knowledge base overview: what documents do on a live call, in plain language and without the API detail.
- Control when the assistant looks up: set when-to-call text after the documents are attached.
- Create and configure an assistant: the assistant you attach these documents to, and the rest of its configuration.
- Documents API reference: the generated reference for all ten endpoints, straight from the spec.