Get a signed download URL for a document
Returns a short-lived **signed download URL** for the document plus its metadata — the read counterpart of the presigned upload (`/projects/{id}/documents/upload-url`). The file bytes are fetched out of band by GETting `downloadUrl`; they never transit this JSON response. The Project-Information audience rule is enforced: a document the caller may not see returns `404` (indistinguishable from a missing one). `?kind` optionally asserts the library (`ohs`/`info`); a mismatch is `404`.
Returns a short-lived signed download URL for the document plus its
metadata — the read counterpart of the presigned upload
(/projects/{id}/documents/upload-url). The file bytes are fetched out of
band by GETting downloadUrl; they never transit this JSON response. The
Project-Information audience rule is enforced: a document the caller may
not see returns 404 (indistinguishable from a missing one). ?kind
optionally asserts the library (ohs/info); a mismatch is 404.
The per-tenant API key, copied from Settings → API & integrations.
Sent as the x-api-key request header. The key is tenant-scoped and acts
with Admin-equivalent, tenant-wide access.
In: header
Path Parameters
Resource id.
Query Parameters
Optionally assert the document's library.
Response Body
application/json
application/json
application/json
application/json
curl -X GET "https://example.com/documents/497f6eca-6276-4993-bfeb-53cbbbba6f08/download-url"{ "id": "497f6eca-6276-4993-bfeb-53cbbbba6f08", "kind": "ohs", "title": "string", "projectId": "5a8591dd-4039-49df-9202-96385ba3eff8", "downloadUrl": "http://example.com", "filename": "string", "fileKind": "pdf"}{ "error": { "code": "unauthorized", "message": "Missing or invalid API key." }}{ "error": { "code": "not_found", "message": "Not found." }}{ "error": { "code": "validation", "message": "One or more inputs are invalid.", "fields": { "fieldName": "A message explaining what's wrong with this field." } }}Register an uploaded document POST
**Step 2 of the two-step presigned document upload.** Call this *after* you have PUT the file bytes to the `uploadUrl` from `POST /projects/{id}/documents/upload-url`. Pass the `uploadRef` from step 1 (or the `storagePath` + `kind`) plus the `title` (and `audience` for `kind: info`). Verifies the object actually landed in storage (and re-checks its real size/type against the ≤25 MB, PDF/JPG/PNG cap), then creates the document record. The new document then appears in `GET /documents?projectId={id}`. Reading document metadata stays on the read-only `/documents` collection; this project-scoped route is the upload (write) side. Admin or the project's Site Manager; blocked on read-only/archived projects.
Request a presigned document upload URL POST
**Step 1 of the two-step presigned document upload.** Validates the declared file (PDF/JPG/PNG, ≤25 MB intent) and returns a short-lived, single-use **signed PUT URL** (`uploadUrl`), the computed `storagePath`, and an opaque `uploadRef`. **The binary bytes never travel through this API call.** After this returns, the caller PUTs the raw file bytes directly to `uploadUrl` (a plain HTTP `PUT` with the file as the request body and the matching `Content-Type`) — out of band — then calls `POST /projects/{id}/documents` with the `uploadRef` to register the document (step 2). This is how binary upload is supported via the API/MCP without bytes passing through the JSON request (ADR 0001 §5). Admin or the project's Site Manager; blocked on read-only/archived projects. `audience` applies to Project Information (`kind: info`) only.