Skip to main content
Errors return a non-2xx status code and a JSON body with a machine-readable error code, a human-readable message, and the numeric status:
{
  "error": "INSUFFICIENT_SCOPE",
  "message": "Partner does not have the required scope: tours.read",
  "status": 403
}
Authentication failures raised before your request reaches the application (at the gateway authorizer) return a bare body without an error field — { "message": "Unauthorized" } for 401 (no API key) or { "message": "Forbidden" } for 403 (a present-but-invalid key, or a bad/missing bearer token alongside a key). Always branch on the HTTP status code, not only on the body shape.

Status codes

StatusCodeWhen
400INVALID_JSONRequest body is not valid JSON
400VALIDATION_ERRORA field failed validation (e.g. empty tour name, bad date range, invalid time zone)
400BAD_REQUEST / GRAPHQL_ERRORThe underlying service rejected the input (missing required field, invalid value)
400"400"Tour creation rejected because the organization has reached its free-tour limit (message: “Organization has reached the free tour limit”). Note the non-semantic error code of "400".
401Missing API key/secret, or missing/invalid/expired bearer token. Returned as a bare { "message": "Unauthorized" }.
402SUBSCRIPTION_REQUIREDWrite operation on an organization without an active subscription
403ForbiddenAn API key is present but the request fails the gateway authorizer — the key is unknown/invalid/revoked, or the bearer token is missing/invalid alongside a key. Returned as a bare { "message": "Forbidden" }.
403INSUFFICIENT_SCOPEYour partner credential lacks the endpoint’s required scope
403UnauthorizedThe authenticated user is not allowed to access this resource
404NOT_FOUNDNo endpoint matches the method + path
404TOUR_NOT_FOUNDThe referenced tour has been deleted
405METHOD_NOT_ALLOWEDPath exists but not for this HTTP method (see the Allow response header)
409GRAPHQL_ERRORThe request conflicts with existing data
413PAYLOAD_TOO_LARGERequest body exceeds 512 KB
500INTERNAL_ERRORUnexpected server error
502EMPTY_RESPONSEThe underlying service returned no data

Response body examples

Concrete examples of each error response body:
// 400 — Validation error
{
  "error": "VALIDATION_ERROR",
  "message": "Tour name must not be empty",
  "status": 400
}
// 401 — Unauthorized (raised before reaching the application; no `error` field)
{
  "message": "Unauthorized"
}
// 402 — Subscription required
{
  "error": "SUBSCRIPTION_REQUIRED",
  "message": "This organization does not have an active subscription.",
  "status": 402
}
// 403 — Insufficient scope
{
  "error": "INSUFFICIENT_SCOPE",
  "message": "Partner does not have the required scope: tours.write",
  "status": 403
}
// 404 — Tour not found (soft-deleted)
{
  "error": "TOUR_NOT_FOUND",
  "message": "Tour has been deleted",
  "status": 404
}
// 405 — Method not allowed
{
  "error": "METHOD_NOT_ALLOWED",
  "message": "PATCH is not supported for /v1/tours. Allowed: GET, POST, PUT, DELETE",
  "status": 405
}
// 413 — Payload too large
{
  "error": "PAYLOAD_TOO_LARGE",
  "message": "Request body exceeds 512KB limit",
  "status": 413
}

Business rules that can block a request

Beyond authentication and scopes, several rules are enforced before a write reaches Daysync’s services:

Subscription requirement (402)

Write operations against an organization that has no active subscription are rejected with 402 SUBSCRIPTION_REQUIRED. Organizations flagged as unlimited-free, trial, or beta are exempt. This applies to writes where the organization is identified by the request — in practice, tour creation (POST /v1/tours).

Deleted tours (404)

Any request that references a tour which has been soft-deleted returns 404 TOUR_NOT_FOUND — for both reads of its children and writes against it.

Input validation (400)

Tour create/edit requests are validated up front:
  • name must not be empty and must be ≤ 500 characters,
  • end_date must not be before start_date,
  • timeZone must be a valid IANA time zone (e.g. Australia/Sydney).
Other endpoints are validated by the underlying service, which surfaces a 400 with a descriptive message.
Known issue: some schedule-item validation failures are currently returned with HTTP 500 and error: "VALIDATION_ERROR" rather than 400. For example, sending startTime as an ISO 8601 datetime instead of HH:mm returns 500 with “Start Time must be in HH:mm format (24-hour format)”. Treat a 500 whose body carries error: "VALIDATION_ERROR" as an input error (fix the request) rather than a transient server fault to retry.

Error messages are sanitized

Internal errors (database constraints, SQL details, internal paths) are never returned verbatim. Such cases are collapsed to a generic message and typically a 409. Build error handling around the status and error code rather than parsing message text.

Handling tips

  • Treat 401 as “fix your credentials/token” and 403 as “you are authenticated but not permitted” — they require different responses (re-auth vs. request more scopes).
  • On 402, surface a subscription/billing prompt to the user; retrying will not help.
  • On 5xx, retry with backoff; these are transient or upstream.