Skip to content

Errors and limits

Most routes: JSON, one error string, written to be shown to a person.

{ "error": "no such session" }

Schema validation failures return the validator’s shape, where message is a JSON string of the field errors:

{
"success": false,
"error": {
"name": "ZodError",
"message": "[{\"code\":\"too_small\",\"path\":[\"photos\"],\"message\":\"Too small: expected array to have >=1 items\"}]"
}
}

Plain text in two cases: bearer failures (Unauthorized), and the byte routes, which return a bare 404 Not Found.

Status Means
400 Bad body or value, or a malformed Authorization header. Retrying is pointless.
401 Missing or wrong key, or no staff session.
404 No such event, set, session or gallery — or the gallery is not live yet.
409 Session already completed or cancelled, or completed concurrently. Re-read the queue.
413 Body over the contact form’s 16 KB.
422 Photo keys not in R2. missing lists them; nothing was written.
429 Over a rate limit. Wait a minute; there is no Retry-After.
500 Ours. Retry with backoff, then tell us.
502 / 503 Mail provider refused, or unconfigured.

Presigned PUTs answer from R2, not from this API: S3-shaped XML, and a 403 there is almost always a Content-Type that does not match the signature.

Routes Limit
POST /api/auth/sign-in/* 10/min
POST /sets/:setId/sessions, POST /track, POST /contact, GET /gallery/:sessionId/zip 60/min
Everything else, /booth/* included none

Keyed on CF-Connecting-IP and counted per colo, not globally: a lock on the door, not flood protection. 60 is loose because a venue shares one NAT IP.

{ "error": "Too many requests. Try again in a minute." }