Guide

Errors

The Trybe API uses conventional HTTP status codes to signal the result of a request, and returns a JSON body describing the failure. Every error response is machine-readable; you should never need to scrape an HTML page or parse a free-form string to recover.

Response shape

Most errors return a single-field envelope:

{
  "message": "A description of the error"
}

Validation errors add an errors map keyed by field name. Each field maps to an array of human-readable messages — multiple rules can fail at once on the same field:

{
  "message": "The request didn't pass validation",
  "errors": {
    "email": ["The email must be a valid email address."],
    "starts_at": [
      "The starts_at field is required.",
      "The starts_at must be a date after now."
    ]
  }
}

The message field is always present and always a string. errors is only present on 422 responses. No other fields are guaranteed across error types — don't read them.

Status codes

Trybe uses the standard HTTP status code set. Successful responses fall in the 2xx range; failures in 4xx (your side) or 5xx (our side).

Status Name When you see it
200 OK Success The request returned a body.
201 Created Created A POST created a new resource. The response body is the new resource.
204 No Content Success, no body A DELETE or some PUT/PATCH operations. There is no response body.
400 Bad Request Bad request The request was malformed — invalid JSON, a missing required body, or a parameter the server couldn't parse.
401 Unauthorized Unauthenticated No token, an invalid token, or the token was revoked.
403 Forbidden Forbidden The token is valid, but the user doesn't have permission for this action on this resource.
404 Not Found Not found The path doesn't exist, or the resource ID doesn't exist (or isn't visible to the caller).
409 Conflict Conflict The request couldn't be applied because of a state conflict — e.g. attempting to cancel an order that's already settled. Not every endpoint uses this; check the endpoint reference.
422 Unprocessable Entity Validation failed The request was syntactically fine, but one or more fields broke validation rules.
5xx Server error Something on our side broke. Retry with backoff; if it persists, contact support.

We do not currently enforce a rate-limit error code (429); see rate limits for the current posture and the backoff patterns you should build anyway.

Concrete error bodies

The exact JSON for each error type, taken from the OpenAPI definitions you can test against in any environment.

400 Bad Request

{
  "message": "The request was malformed."
}

Triggered by syntactically invalid input — broken JSON, an unparseable date, or a query parameter the server can't read. The body is informational; don't write code that branches on its text.

401 Unauthorized

{
  "message": "Unauthenticated"
}

The token is missing, malformed, or no longer valid. See authentication for the header shape and the most common causes.

403 Forbidden

{
  "message": "This action is unauthorized."
}

The token authenticates a real user, but that user doesn't have permission for the requested action on the requested resource. Permissions in Trybe are per-site; the most common cause is that the integration user hasn't been added to a new site yet.

404 Not Found

{
  "message": "The requested resource could not be found"
}

Returned for two cases that look identical from the outside: the resource genuinely doesn't exist, or it exists but the caller can't see it. This is intentional — leaking the difference would help an attacker enumerate IDs.

422 Unprocessable Entity

{
  "message": "The request didn't pass validation",
  "errors": {
    "guests": ["The guests must be at least 1."],
    "starts_at": ["The starts_at field is required."]
  }
}

Use errors to render messages next to the right form fields. The keys match the field names in the request body (or in query parameters for GET filter validation).

5xx

{
  "message": "Server error"
}

Trybe occasionally returns a 5xx while we recover from a downstream issue. Retry idempotent requests (any GET, plus PUT and DELETE calls) with exponential backoff. For non-idempotent operations (POSTs that create resources), check whether the resource was actually created before retrying — many POST endpoints accept an idempotency hint or expose a follow-up GET you can use to confirm state.

Handling errors

The same pattern works everywhere: check the status, branch on it, and pull the message (and any field errors) out of the body.

async function listSites() {
  const res = await fetch('https://api.try.be/sites', {
    headers: {
      Authorization: `Bearer ${process.env.TRYBE_API_KEY}`,
      Accept: 'application/json',
    },
  })

  if (res.ok) {
    return res.json()
  }

  const body = await res.json().catch(() => ({ message: 'Unknown error' }))

  switch (res.status) {
    case 401:
      throw new AuthError(body.message)
    case 403:
      throw new ForbiddenError(body.message)
    case 404:
      throw new NotFoundError(body.message)
    case 422:
      throw new ValidationError(body.message, body.errors ?? {})
    default:
      throw new TrybeError(`${res.status}: ${body.message}`)
  }
}

class ValidationError extends Error {
  constructor(message, fields) {
    super(message)
    this.fields = fields // { [fieldName]: string[] }
  }
}

The same shape in PHP:

try {
    $response = $client->post('/shop/appointments', ['json' => $payload]);
    $data = json_decode((string) $response->getBody(), true);
} catch (GuzzleHttp\Exception\ClientException $e) {
    $status = $e->getResponse()->getStatusCode();
    $body = json_decode((string) $e->getResponse()->getBody(), true);

    if ($status === 422) {
        foreach ($body['errors'] as $field => $messages) {
            $form->addError($field, $messages[0]);
        }
        return;
    }

    throw new TrybeException("$status: {$body['message']}", $status, $e);
}

Designing for resilience

A few patterns to bake in from day one:

  • Branch on status, not on message. The HTTP status code is the contract. The message text may improve over time and isn't versioned.
  • Surface validation errors to users field-by-field. The errors map is designed for this; rendering only message for a 422 discards useful information.
  • Retry 5xx and network errors with exponential backoff and jitter. Cap the number of retries — three is usually plenty — and treat repeated 5xx as signal to alert rather than retry harder.
  • Never retry a 4xx without changing the request. It will fail the same way the second time.
  • Log the response body on unexpected errors. Redact Authorization headers, but keep the response body — message is the fastest way to diagnose what your code sent wrong.

See also