API REFERENCE

The Knowledge API

Give one user their own private documents. Uploads run through the same pgvector hybrid retrieval as your shared knowledge base, but the chunks stay scoped to that user.

These endpoints manage the private documents that belong to a single end user. Every call carries a signed user_context, so the file is indexed under the audience user:<id> and never leaks into another user's retrieval. To seed the shared pool that everyone in a project can see, upload from Settings instead.

All four endpoints require signed identity mode. Upload, list and delete must also include a valid _ts and _sig on the user_context, or you get SIGNED_IDENTITY_REQUIRED; a missing or anonymous id gives USER_ID_REQUIRED. See the signed identity guide for the HMAC recipe.
These routes are origin-checked even server-to-server. If your project has an allowed-origins list, a request with no Origin header is rejected with 403 ORIGIN_NOT_ALLOWED — so a backend curl that works against /v1/credentials, which skips the check on purpose, fails here. Send an Origin header matching one of your allowed origins (a trailing slash on the header is stripped before comparison), or leave the allowlist empty. Every example below sends one.
Per-user documents are PostgreSQL-only. Without DATABASE_URL the server falls back to a local ChromaDB store that has no audience support: an upload with a user audience returns an error, and the list route returns an empty array rather than exposing the shared pool.

Upload a document

POST/v1/knowledge

A multipart/form-data request with two parts: the file itself and a user_context field holding the signed identity as JSON. The file is queued for embedding and the call returns immediately with a job id. Indexing runs in the background (chunk at 400 words with an 80-word overlap, embed with BAAI/bge-m3, write to pgvector), so a fresh upload becomes retrievable a few seconds later.

FieldTypeDescription
filerequiredfileThe document. PDF, TXT, or MD. Up to 50 MB.
user_contextrequiredstring (JSON)The signed identity for the owning user, serialized as JSON. Must include _ts and _sig.

Limits enforced on upload:

FieldTypeDescription
file typeextensionJudged by extension: .pdf, .txt or .md only. Anything else returns 415 UNSUPPORTED_TYPE.
file sizebytesHard cap of 50 MB. A larger file returns 413 FILE_TOO_LARGE.
docs per userintUp to 20 documents per user per project. The 21st returns 409 DOC_CAP_REACHED until one is deleted.
rate limit10/min, 50/dayPer user per project, on a bucket separate from /v1/chat. Over either limit returns 429 RATE_LIMIT_EXCEEDED with a retry-after hint in detail.
indexingkill switchWhile indexing is globally paused, uploads return 503 INDEXING_PAUSED. Retrieval over already-indexed documents is unaffected.
bash
curl -X POST https://agentifys.ai/v1/knowledge \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com" \
  -F "file=@handbook.pdf" \
  -F 'user_context={"id":"u_42","name":"Sara","_ts":1735689600,"_sig":"a1b2c3..."}'

Response. Job ids and document ids are bare 12-character hex strings with no prefix:

json
{
  "job_id": "9f3c1e7a2b8d",
  "status": "processing",
  "filename": "handbook.pdf"
}
FieldTypeDescription
job_idstringHandle for the background indexing job. 12 hex characters.
statusstringAlways "processing" on accept. The document is embedded asynchronously.
filenamestringThe original filename, echoed back.
A 200 here only means the file was accepted, not that it indexed. Extraction failures surface on the job, not on this response: poll the job below, or list the user's documents.

Check upload status

GET/v1/knowledge/jobs/{job_id}

Polls the background indexing job by the job_id returned from the upload. The job is scoped to your project and carries no user_context, so any signed-mode caller holding the project key can read it.

bash
curl https://agentifys.ai/v1/knowledge/jobs/9f3c1e7a2b8d \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com"

Response:

json
{
  "filename": "handbook.pdf",
  "status": "done",
  "chunks": 128,
  "doc_id": "2b8d40f1c7a3",
  "error": null
}
FieldTypeDescription
statusstring"processing" while indexing, "done" when retrievable, "failed" if it errored.
chunksintNumber of chunks written once done. 0 while processing.
doc_idstring | nullThe document id once done. Null until then.
errorstring | nullFailure reason when status is "failed", otherwise null.

The two failures you will actually see are both about extraction, and both arrive as prose in error:

FieldTypeDescription
Scanned PDFfailed"Scanned PDF — N pages found but no text could be extracted." The file is image-based. Convert it to a text PDF or supply the text as .txt.
Too sparsefailed"Document is too sparse to index — N words total, but each chunk needs at least 30 words." Text came out, but no chunk reached the 30-word floor.

List a user's documents

GET/v1/knowledge

Returns the documents whose audience includes the user in the request identity. That is their own uploads plus any document an admin targeted at them from the console. It never includes the shared project pool and never another user's private files.

bash
# The signed user_context goes in the user_context query param, URL-encoded.
curl "https://agentifys.ai/v1/knowledge?user_context=%7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1735689600%2C%22_sig%22%3A%22a1b2c3...%22%7D" \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com"

Response, each document as its id and filename. The audience keys are deliberately not returned, so a user cannot learn who else holds a shared document:

json
{
  "documents": [
    { "doc_id": "9f3c1e7a2b8d", "filename": "handbook.pdf" },
    { "doc_id": "2b8d40f1c7a3", "filename": "notes.md" }
  ]
}

Delete a document

DELETE/v1/knowledge/{doc_id}

Revokes the caller's access to a document. The document id goes in the path and the signed identity is passed as a user_context query param. A user can only act on a document whose audience includes them; anything else returns 404 NOT_FOUND. Either way it frees a slot against the 20-document cap.

This is an audience removal, not always a hard delete. The caller's user:<id> key is stripped from the document, and the chunks are physically deleted only once no recipient is left. A document an admin shared with several users survives for the others.
bash
curl -X DELETE "https://agentifys.ai/v1/knowledge/9f3c1e7a2b8d?user_context=%7B%22id%22%3A%22u_42%22%2C%22_ts%22%3A1735689600%2C%22_sig%22%3A%22a1b2c3...%22%7D" \
  -H "Authorization: Bearer era_your_project_key" \
  -H "Origin: https://app.example.com"

Response:

json
{ "success": true }