Errors and limits
HTTP errors, request limits and what to do when a call fails.
Error format
A failed call answers with an HTTP status code and a body. Most endpoints use this JSON envelope, with a message meant for logs and developers:
{
"success": false,
"data": null,
"message": "form not found"
}
The validation queue uses a minimal shape instead — an error field — and the /mobile-collect channel answers in OpenRosa XML, an OpenRosaResponse element carrying a message, as the mobile apps expect.
Status codes
| Code | Meaning | When it happens |
|---|---|---|
| 400 | Bad request | Invalid payload or a rule the request cannot satisfy: malformed JSON, an unknown field value, a missing comment on a rejection, a registration without a verified email, or an unknown or expired refresh token. |
| 401 | Unauthorized | Missing, invalid, revoked or expired session token. On the OpenRosa endpoints, the first unauthenticated probe is answered this way on purpose (WWW-Authenticate: Basic). |
| 403 | Forbidden | Authenticated but not allowed: you are not a member of the workspace or project, your role is not sufficient, the permission matrix refused the action, or anonymous collection is disabled in the workspace. |
| 404 | Not found | The resource does not exist, or it sits outside your access scope — a form, project, entry or collection link you cannot reach. |
| 409 | Conflict | The request clashes with existing data: an email address that is already registered, a workspace slug already taken, an invitation already pending, a member already present, or a workspace membership that is not active yet. |
| 410 | Gone | An invitation link that was revoked is presented again. |
| 413 | Payload too large | An uploaded document exceeds the workspace size limit (see Size limits below). |
| 422 | Unprocessable entity | The request is well formed but the current state forbids it: an invalid status transition, an edit of an entry that is already validated or rejected, or an invalid region code. |
| 429 | Too many requests | Repeated failed one-time code (OTP) verification attempts: the address is temporarily locked. Returned by POST /auth/otp/verify — a security lockout, not a volume quota. |
| 500 | Internal server error | Something failed on the server side. Retry later; if it persists, contact support. |
| 503 | Service unavailable | A dependency is temporarily unavailable — sign-in provider or email delivery, for example. Retry later. |
Retry 5xx responses and network timeouts with exponential backoff. Do not retry a 4xx error unchanged — fix the request first; a 401 usually means the token must be refreshed or the caller signed in again.
Size limits
| What | Limit |
|---|---|
| Request bodies on the upload routes | 500 MB per request (files, user documents, workspace documents). |
| Workspace documents | The workspace setting maxDocumentSizeMb — 50 MB by default. A larger upload is rejected with 413. |
| Form file uploads | 25 MB per file on /forms/{id}/uploads (base64 payload; JSON bodies up to 26 MB are accepted, and a file above 25 MB is refused with 400). |
| Mobile submissions | 100 MB, advertised to KoboCollect and ODK Collect through X-OpenRosa-Accept-Content-Length. |
No request quota
Zottle applies no request quota: there is no per-minute call limit and no volume-based throttling on API requests. What can fail is payload size or permissions, never throughput. One exception exists: POST /auth/otp/verify answers 429 after repeated failed attempts — a security lockout that expires on its own (see Status codes).