Request a presigned document upload URL
**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.
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.
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.
Request Body
application/json
TypeScript Definitions
Use the request body type in TypeScript.
Response Body
application/json
application/json
application/json
application/json
application/json
curl -X POST "https://example.com/projects/497f6eca-6276-4993-bfeb-53cbbbba6f08/documents/upload-url" \ -H "Content-Type: application/json" \ -d '{ "kind": "ohs", "title": "string", "filename": "string", "contentType": "string" }'{ "uploadRef": "string", "storagePath": "string", "uploadUrl": "string", "bucket": "string", "kind": "ohs"}{ "error": { "code": "unauthorized", "message": "Missing or invalid API key." }}{ "error": { "code": "read_only", "message": "Your subscription is inactive. This action is read-only." }}{ "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." } }}Get a signed download URL for a document GET
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`.
Get a form submission GET
Reads one submission with its frozen template snapshot and answers.