Create knowledge document from PDF
Adds a document to the brand’s Knowledge Base from a PDF file. One call: the file is uploaded and
vision-extracted to text automatically, matching what a PDF upload through the app does.
file_base64 is capped at 25MB decoded. Processing 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-documents/pdfAuthorization
| 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 |
file_base64 | string | required | The PDF file, base64-encoded. 25MB cap on the decoded bytes |
file_name | string, up to 255 chars | optional | Defaults to the document id |
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/pdf' \
--header 'Authorization: Bearer <api-key>' \
--header 'Idempotency-Key: 5b0e77d2-13a9-4f6a-8ce1-9a3f5e21c0aa' \
--header 'Content-Type: application/json' \
--data '{"title": "2026 Product Spec Sheet", "user_description": "Official spec sheet for the enterprise tier.", "file_base64": "JVBERi0xLjQKJ...", "file_name": "product-spec-2026.pdf"}'Response
200
{
"data": {
"document": {
"id": "d9e3a1c7-4f8b-4a2d-9c01-7e5f3b9a2d68",
"brand_id": "242546de-4cee-4a7d-952e-f6aa60c63ef8",
"title": "2026 Product Spec Sheet",
"user_description": "Official spec sheet for the enterprise tier.",
"source_type": "pdf",
"status": "processing",
"error_message": null,
"char_count": 0,
"chunk_count": 0,
"token_count": 0,
"needs_reembed": false,
"file_name": "product-spec-2026.pdf",
"page_count": null,
"file_size_bytes": 812044,
"mime_type": "application/pdf",
"material_type": null,
"writing_style_id": null,
"style_prompt_dismissed": false,
"created_by": "a1b2c3d4-5e6f-4a7b-8c9d-0e1f2a3b4c5d",
"created_at": "2026-08-20T09:20:11.000Z",
"updated_at": "2026-08-20T09:20:11.000Z"
}
},
"request_id": "req_2b8f1a0c9e3d47f5a6b8"
}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 pdf 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. 0 until vision extraction completes |
data.document.chunk_count | integer | required. 0 until vision extraction completes |
data.document.token_count | integer | required. 0 until vision extraction completes |
data.document.needs_reembed | boolean | required |
data.document.file_name | string | required |
data.document.page_count | integer | null | required. null until vision extraction completes |
data.document.file_size_bytes | integer | required. Size of the decoded PDF, in bytes |
data.document.mime_type | string | required. Always application/pdf |
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, file_base64 that isn’t valid base64, a decoded file that is empty, a decoded file over 25MB, a decoded file whose first bytes aren’t %PDF, 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 | The workspace is already at 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
This is one call, not a two-step upload. The app’s browser flow signs a Storage URL, uploads
from the client, then confirms — built that way to keep large files off the Next.js server. That
constraint doesn’t apply to a server-to-server call, so this endpoint just accepts the file’s raw
bytes in file_base64 and writes them to Storage itself with a service-role client.
The 25MB cap is on the decoded PDF, not the request body. Base64 inflates the wire size by roughly a third, so the JSON body you send will be noticeably larger than 25MB for a file near the limit. Budget for that if you enforce a request-size ceiling on your own side.
Extraction is vision-based and asynchronous. The document is returned with
status: "processing" and char_count: 0, page_count: null before extraction has run. Poll
Get knowledge document and read raw_text, char_count
and page_count once status flips to ready.
Workspace storage is checked up front, before the upload. The PDF’s file bytes themselves don’t count against the 50MB knowledge base cap, only its extracted text will, once ingested. A workspace already at the cap is still rejected before the upload happens, so you’re not charged for an upload and a vision extraction that would fail regardless.
writing_style_id overrides user_description, the same as on
Create knowledge document: your value is replaced with
an auto-generated description naming the style, and the per-style document cap still applies.
Idempotency-Key is required. Reuse it when retrying an attempt and the original response is
replayed rather than a second document being uploaded and created. See
Idempotency.