Request a presigned diary-photo upload URL
**Step 1 of the presigned diary-photo upload** — the image-only sibling of the document upload. Validates the declared image (JPG/PNG only — no PDF) and returns a short-lived, single-use **signed PUT URL** (`uploadUrl`) plus the computed `storagePath`. **The image bytes never travel through this API call.** After this returns, the caller PUTs the raw image bytes directly to `uploadUrl` (a plain HTTP `PUT` with the file as the request body and the matching `Content-Type`) — out of band — then passes the returned `storagePath` in the `photoPaths` array of `POST /diary-entries`. **There is no register step**: creating the diary entry writes the `diary_photo` row(s) (ADR 0001 §5). Any crew member assigned to the project may log diary photos (not manager-only); an Admin/API key is implicitly on every project. Blocked on read-only/archived projects. **Photos must be 2 MB or smaller.** The limit is enforced by Storage on the PUT itself, so an oversized upload fails at that step rather than here — this call only sees the *declared* file. Resize before uploading: 1600px on the long edge is plenty, and is what the FOREMAN app sends.
Step 1 of the presigned diary-photo upload — the image-only sibling
of the document upload. Validates the declared image (JPG/PNG only — no
PDF) and returns a short-lived, single-use signed PUT URL
(uploadUrl) plus the computed storagePath.
The image bytes never travel through this API call. After this
returns, the caller PUTs the raw image bytes directly to uploadUrl (a
plain HTTP PUT with the file as the request body and the matching
Content-Type) — out of band — then passes the returned storagePath in
the photoPaths array of POST /diary-entries. There is no register
step: creating the diary entry writes the diary_photo row(s) (ADR
0001 §5).
Any crew member assigned to the project may log diary photos (not manager-only); an Admin/API key is implicitly on every project. Blocked on read-only/archived projects.
Photos must be 2 MB or smaller. The limit is enforced by Storage on the PUT itself, so an oversized upload fails at that step rather than here — this call only sees the declared file. Resize before uploading: 1600px on the long edge is plenty, and is what the FOREMAN app sends.
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/diary-photos/upload-url" \ -H "Content-Type: application/json" \ -d '{ "filename": "string", "contentType": "string" }'{ "storagePath": "string", "uploadUrl": "string", "bucket": "string"}{ "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." } }}Edit is not available via the API PUT
Editing a diary entry stays in the app; this verb is answered with a `405` pointing there.
Edit is not available via the API PATCH
Editing a diary entry stays in the app (spec: manual edits happen on the entry's detail page, where the role × field matrix and the entry's History live). This verb is answered with a `405` pointing there.