Create knowledge document
Adds a document to the brand’s Knowledge Base from pasted text — the same as “Add Document ->
Paste Text” in the app. Processing (summarize, chunk, embed) happens in the background: the
document is returned with status: "processing". Poll
Get knowledge document until it flips to ready or
failed.
Requires the write:knowledge_base scope, and the workspace must be enabled for API writes.
POST /v1/brands/{brand_id}/knowledge-documentsAuthorization
| Header | Type | Required |
|---|---|---|
Authorization | Bearer asky_sk_… | required |
Idempotency-Key | string | required |
Path parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
brand_id | uuid | required | From List brands |
Body
application/json
| Field | Type | Required | Description |
|---|---|---|---|
title | string, 1-200 chars | required | |
user_description | string, up to 2000 chars | optional | How this document should be used, in your own words. Ignored (silently replaced) when writing_style_id is set |
text | string, 1-1,000,000 chars | required | Pasted content, markdown or plain text. Roughly a 1MB cap, matching the app |
writing_style_id | uuid | optional | Attach as a writing-style reference document instead of general brand knowledge |
Request
cURL
curl --request POST \
--url 'https://api.askylabs.com/v1/brands/242546de-4cee-4a7d-952e-f6aa60c63ef8/knowledge-documents' \
--header 'Authorization: Bearer <api-key>' \
--header 'Idempotency-Key: 5b0e77d2-13a9-4f6a-8ce1-9a3f5e21c0aa' \
--header 'Content-Type: application/json' \
--data '{"title": "Product Positioning FAQ", "user_description": "Internal FAQ used to ground content about our differentiation.", "text": "## What makes us different?\n\nAsky tracks how a brand appears across AI search engines and turns the gaps into content."}'Response
200
{
"data": {
"document": {
"id": "b4f7e2a1-9c3d-4e6f-8a1b-2c5d7e9f0a3b",
"brand_id": "242546de-4cee-4a7d-952e-f6aa60c63ef8",
"title": "Product Positioning FAQ",
"user_description": "Internal FAQ used to ground content about our differentiation.",
"source_type": "paste",
"status": "processing",
"error_message": null,
"char_count": 4820,
"chunk_count": 0,
"token_count": 0,
"needs_reembed": false,
"file_name": null,
"page_count": null,
"file_size_bytes": null,
"mime_type": null,
"material_type": null,
"writing_style_id": null,
"style_prompt_dismissed": false,
"created_by": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"created_at": "2026-08-20T09:14:22.000Z",
"updated_at": "2026-08-20T09:14:22.000Z"
}
},
"request_id": "req_7d21b0af59c34e18b6ac"
}Response fields
| Field | Type | Description |
|---|---|---|
data.document.id | uuid | required. Use it with Get knowledge document |
data.document.brand_id | uuid | required |
data.document.title | string | required |
data.document.user_description | string | null | required |
data.document.source_type | string | required. Always paste for this endpoint |
data.document.status | string | required. processing, ready or failed |
data.document.error_message | string | null | required. Set once status is failed |
data.document.char_count | integer | required. Length of the pasted text |
data.document.chunk_count | integer | required. Always 0 at creation, populated once ingest completes |
data.document.token_count | integer | required. Always 0 at creation, populated once ingest completes |
data.document.needs_reembed | boolean | required |
data.document.file_name | string | null | required. Always null for a pasted-text document |
data.document.page_count | integer | null | required. Always null for a pasted-text document |
data.document.file_size_bytes | integer | null | required. Always null for a pasted-text document |
data.document.mime_type | string | null | required. Always null for a pasted-text document |
data.document.material_type | string | null | required. A content classification set once processing completes. null at creation |
data.document.writing_style_id | uuid | null | required |
data.document.style_prompt_dismissed | boolean | required |
data.document.created_by | uuid | required |
data.document.created_at | string | required. ISO 8601 |
data.document.updated_at | string | required. ISO 8601 |
request_id | string | required. Also returned as the x-request-id header. Quote it when contacting support |
Errors
| Status | Code | When |
|---|---|---|
| 400 | invalid_request | Unknown field, empty or over-200-character title, user_description over 2000 characters, empty or over-1,000,000-character text, or a writing_style_id that doesn’t exist or belongs to another brand |
| 401 | invalid_token | The key is missing, malformed, or does not exist |
| 401 | token_expired | The key passed its expiry date |
| 401 | token_revoked | The key was revoked |
| 403 | insufficient_scope | The key does not carry write:knowledge_base |
| 403 | plan_required | This workspace is not enabled for API writes |
| 403 | plan_required | Brand Knowledge is not included in this workspace’s current plan |
| 403 | quota_exceeded | This document would push the workspace over its 50MB knowledge base storage limit |
| 404 | not_found | The brand does not exist, or this key is not allowed to reach it |
| 429 | rate_limited | Over 60 requests per minute |
plan_required appears twice with different messages. The first means this workspace’s plan does
not include API writes at all. The second means writes are enabled but Brand Knowledge specifically
is not on the plan.
Notes
writing_style_id overrides user_description. When you attach a document to a writing style,
your user_description is ignored and replaced with an auto-generated description naming the
style. There is a per-style cap on how many documents can be attached; going over it returns
400 invalid_request naming the reason.
The 50MB cap is on total knowledge base text, per workspace, not per document. It is checked
against the UTF-8 byte length of text before anything is written, so a call that would tip the
workspace over the limit fails cleanly with 403 quota_exceeded rather than partially writing.
Processing runs in the background. The document is returned with status: "processing" before
summarizing, chunking and embedding have happened. Poll
Get knowledge document until status flips to ready or
failed.
A queue failure after the document is written still leaves the document behind. In the rare
case where the background job cannot be queued, the call itself fails, but the document row
persists with status: "failed" and an error_message pointing at
asky_knowledge_document_resync. A caller polling by title rather than by the id from this
response would still find it.
No URL or import ingestion here. This endpoint only covers pasted text; there is no PDF-free equivalent for a URL or a Notion-style import over this API. URL/scrape ingestion was removed from the app itself. Pulling one item from a connection Asky stored via OAuth (an MCP import) is also out of scope here, but if you already have your own access to that source, reading the content yourself and calling this endpoint gets you the same result.
Idempotency-Key is required. Reuse it when retrying an attempt and the original response is
replayed rather than a second document being created. See Idempotency.