Attach a file to a Kai thread
const url = 'http://localhost:8000/api/support/threads/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/files/';const form = new FormData();form.append('file', 'file');
const options = {method: 'POST', headers: {Authorization: 'Bearer <token>'}};
options.body = form;
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url http://localhost:8000/api/support/threads/2489E9AD-2EE2-8E00-8EC9-32D5F69181C0/files/ \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: multipart/form-data' \ --form file=@fileMultipart upload (file field, ≤5 MB, ≤20 unsent files per thread). Accepted: png / jpeg / webp images and plain-text formats (txt, csv, markdown, json). The file is stored in the media bucket and joins the thread’s context: text files feed a bounded excerpt into Kai’s prompt; images are named but not analysed (no vision model yet — Kai will ask the user to describe a screenshot when it matters). A 400 with file_too_large / file_type_unsupported / too_many_files rejects an invalid upload. Feature-flagged like every thread route (404 while disabled).
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Request Body
Section titled “Request Body”object
Responses
Section titled “Responses”Context chip on a thread — label + kind. The snapshot content stays server-side (it is prompt material, not display material); user file attachments additionally expose a short-lived download URL + media type so the FE can render an image thumb.
object
Examplegenerated
{ "kind": "example", "label": "example", "subject_id": "2489E9AD-2EE2-8E00-8EC9-32D5F69181C0", "captured_at": "2026-04-15T12:00:00Z"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Examplegenerated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Examplegenerated
{ "detail": "example"}object
Human-readable message, or a stable machine code for the cases a client branches on. The standard envelope for 400 (validation — a field-keyed object may appear instead), 401 (missing / invalid credentials), 403 (authenticated but not permitted), and 404 (absent — cross-team records are collapsed to 404 so the API never leaks the existence of another team’s data).
Examplegenerated
{ "detail": "example"}object
429 — rate limit exceeded. Honour the Retry-After response header (seconds to wait) before retrying; it is sent on every 429.
Examplegenerated
{ "detail": "example"}