Errors

Every error response uses the same JSON envelope:

{
  "error": {
    "code": "validation_error",
    "message": "url is required",
    "correlation_id": "8c8a1b80-6c97-4f5e-8d0a-3a25d1e8e4c1"
  }
}

Map HTTP status to error.code in your client so product UI and logs stay consistent. Always persist correlation_id when you surface failures to support.

Prerequisites

  • Ability to inspect response bodies and headers from your HTTP client.
  • Basic auth setup (Authentication).
  • Awareness of Rate limits for 429 handling.

Status to code mapping

HTTP statuserror.codeMeaning
400validation_errorThe request body, query, or headers failed validation
401unauthorizedThe token is missing, malformed, expired, or revoked
403forbiddenThe token is valid but lacks permission for this resource
404not_foundThe resource does not exist (or the caller cannot see it)
409conflictThe request conflicts with current state (e.g. duplicate name)
429rate_limitedA rate limit was exceeded; see Rate limits
503plan_limit_exceededA plan quota or limit was reached. The body includes an upgrade.url link
5xxinternal_errorSomething failed on our side. Retry with backoff and report correlation_id

Plan-limit responses

When error.code = "plan_limit_exceeded", the body has an additional field:

{
  "error": {
    "code": "plan_limit_exceeded",
    "message": "Workspace bookmark limit reached on the Free plan.",
    "correlation_id": "...",
    "upgrade": {"url": "https://not24get.me/billing/upgrade"}
  }
}

Surface the upgrade.url to the user; that is the canonical place to upgrade.

Always log correlation_id

If you open a support ticket about a failed request, include the correlation_id from the response (or from the X-Correlation-ID response header). It lets us find the exact server-side trace for that call.

Client pseudocode

async function callApi<T>(req: Request): Promise<T> {
  const res = await fetch(req);
  if (res.ok) return res.json() as Promise<T>;
  const body = await res.json().catch(() => ({}));
  const err = body?.error ?? {};
  throw Object.assign(new Error(err.message ?? res.statusText), {
    status: res.status,
    code: err.code,
    correlationId: err.correlation_id,
  });
}

Troubleshooting

  • Generic network failure with no JSON — retry once, then check TLS/proxy settings. When JSON is present, prefer error.code over status text alone.
  • 401 after a working day — JWT expired or API key revoked; refresh or recreate credentials (Authentication).
  • 404 for a resource you just created — wrong workspace header, or the caller cannot see the resource. Confirm X-Workspace-ID.
  • 409 conflict on create — duplicate name or state; adjust the payload rather than retrying blindly.
  • 429 loops — wait for Retry-After; do not retry other 4xx codes.

Mapping errors to UX

Show error.message to end users when it is safe, and keep error.code for programmatic branches. For plan_limit_exceeded, prefer the upgrade.url CTA over a generic failure toast. For internal_error, offer retry with backoff and include correlation_id in any support form so engineering can find the trace quickly.