Upload a document
Uploads a document by sending its bytes inline as base64 in the JSON body (capped at roughly 12 MiB of file content), together with filename, content type, scope (private, channel, or team; defaults to private), and optional channel_id, category, tags, and caption. The operation is content-addressed and idempotent per owner and scope: re-uploading identical bytes returns the existing document rather than creating a duplicate, and any newly supplied tags, caption, or category are merged onto that existing row (reflected by deduplicated and enriched flags in the response). channel_id is required when scope is channel and rejected otherwise. Returns the document id, download URL, SHA-256, and size. Requires the documents:write scope.
Authorizations
Personal Access Token. Send as Authorization: Bearer hq_pat_....
Body
Base64-encoded payload. 16 MiB cap on the JSON body limits raw to ~12 MiB - the multipart streaming path lands in pass 2.
Free-form user category; the auto-categoriser may overwrite this in a follow-up worker pass once we have Haiku wired.
Required iff scope == "channel".
private | channel | team. Defaults to private.
Response
Document saved
True iff this hashed identical to an existing document in this tenant - we still record the new metadata row but the Trove object is shared. The UI surfaces this as a "saved (dedup'd against existing copy)" hint.
x >= 0True iff this was a same-owner/same-scope dedup AND the caller supplied new metadata (tags/caption/category) that we actually merged onto the existing row. Lets the UI say "already in your library - details updated" honestly vs a bare "already there".