Errors

Every non-2xx response from /api/v1/* uses the same envelope — a single error object with a machine-readable code, a human message, the current request_id, and (optionally) the offending field or a docs link.

#Envelope

json
{
  "error": {
    "code": "tier_insufficient",
    "message": "This endpoint requires pro tier or higher",
    "field": "...",
    "request_id": "req_01HX...",
    "docs_url": "https://closeline.io/docs/errors"
  }
}

The x-request-id response header is always present and matches error.request_id — terminal clients can copy it from either.

#Codes

StatusCodeSDK classWhen
401unauthorizedAuthErrorMissing, malformed, or expired token.
403tier_insufficientTierErrorValid token but the tier doesn't cover this endpoint.
404not_foundNotFoundErrorResource unknown. Owner-scoped endpoints return 404 (not 403) for non-owners.
422invalid_requestValidationErrorBody or query parameters failed validation. `field` points at the offending key when available.
429rate_limitRateLimitErrorPer-tier limit hit. `Retry-After` header carries the cooldown in seconds.
500internal_errorApiErrorServer-side bug. Retry with exponential backoff; open a support ticket if it persists.

#Handling in the SDK

Every error the SDK throws is an ApiError subclass — you can narrow with instanceof:

ts
import {
  Closeline,
  ApiError,
  AuthError,
  TierError,
  NotFoundError,
  ValidationError,
  RateLimitError,
} from "@closeline/sdk";

const cl = new Closeline({ apiKey: process.env.CLOSELINE_API_KEY! });

try {
  await cl.signals.list({ min_strength: 0.9 });
} catch (err) {
  if (err instanceof RateLimitError) {
    // Honour Retry-After
    await sleep((err.retryAfterSec ?? 1) * 1000);
    return;
  }
  if (err instanceof TierError) {
    // Upgrade prompt
    return;
  }
  if (err instanceof ApiError) {
    console.error(err.code, err.status, err.requestId);
    throw err;
  }
  throw err;
}

#Retry semantics

  • 429 — back off for the Retry-After seconds (available as err.retryAfterSec).
  • 5xx — exponential backoff with jitter. Always safe to retry idempotent methods (GET, DELETE).
  • POST / PATCH — pass Idempotency-Key so a retry never creates a duplicate row.
  • 4xx (other) — do NOT retry blindly. Surface the error to the user.