Errors and status codes
The CampOne API uses standard HTTP status codes and two predictable JSON error shapes. Read this page once and you will be able to handle errors uniformly across every endpoint.
Error response shapes
Section titled “Error response shapes”The API returns one of two JSON shapes when something goes wrong.
Top-level detail
Section titled “Top-level detail”Used for authentication, authorization, not-found, throttling, server errors, and most domain-level errors:
{ "detail": "invalid_key" }The value is either a short machine code (e.g. invalid_key, site_not_found) or a human-readable sentence — see the documented error codes below.
Field-keyed validation errors
Section titled “Field-keyed validation errors”POST endpoints validate the request body before processing. If validation fails, the API returns 400 Bad Request with one entry per offending field:
{ "check_in_date": ["This field is required."], "num_adults": ["Ensure this value is greater than or equal to 0."]}Each value is an array of one or more error messages for that field. Top-level non_field_errors may also appear if the validation rule isn’t tied to a single field.
Status code reference
Section titled “Status code reference”| Code | When |
|---|---|
200 OK | Successful GET |
201 Created | Successful POST that created a resource |
400 Bad Request | Validation error on the body, or a documented domain error such as site_not_found |
401 Unauthorized | Missing, malformed, expired, or revoked API key |
403 Forbidden | Valid key but missing the required scope |
404 Not Found | The resource does not exist, or it exists but belongs to a different tenant |
429 Too Many Requests | Per-key rate limit exceeded — see Rate limits |
500 Internal Server Error | Server-side fault — retry with exponential backoff and report to support if it persists |
Documented error codes
Section titled “Documented error codes”The codes below are stable strings — your code can match on them without parsing prose. Anything else that lands in detail should be treated as a generic message, not a contract.
| Code | Status | Where |
|---|---|---|
invalid_key | 401 | Any endpoint, when the bearer token is missing, malformed, or doesn’t match a key on file |
key_inactive | 401 | Any endpoint, when the key has been revoked or has passed its expiry |
site_not_found | 400 | POST /api/v1/public/bookings/, when site_id does not match a site on the calling tenant |
not_found | 404 | Detail endpoints (e.g. GET /api/v1/public/bookings/{id}/), when the resource doesn’t exist or belongs to another tenant |
Tenant isolation
Section titled “Tenant isolation”Every API key is bound to exactly one tenant. The API filters all queries by that tenant before they reach the database, so:
- A
GETfor a resource that exists but belongs to a different tenant returns404, not403. The API deliberately does not distinguish “not yours” from “doesn’t exist” — that would leak the existence of resources across tenants. - Resource IDs are not globally unique across tenants. Booking ID
42for tenant A and booking ID42for tenant B are different bookings. Never assume an ID you obtained from one key is meaningful with another. - A
POSTbody that references an ID belonging to a different tenant (e.g.site_idon a booking creation) is rejected as if the ID didn’t exist (400 site_not_found).
Idempotency
Section titled “Idempotency”The current API version (v1) does not support idempotency keys. In particular:
POST /api/v1/public/bookings/is not safe to retry blindly — a network timeout that obscures a successful creation will leave you with a duplicate booking on retry.- If a
POSTtimes out, query the list endpoint with appropriate filters before retrying, to confirm whether the resource was actually created.
A proper idempotency-key header is on the roadmap. Until it ships, treat creation calls as side-effecting and design your retry logic accordingly.