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
{
"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
| Status | Code | SDK class | When |
|---|---|---|---|
401 | unauthorized | AuthError | Missing, malformed, or expired token. |
403 | tier_insufficient | TierError | Valid token but the tier doesn't cover this endpoint. |
404 | not_found | NotFoundError | Resource unknown. Owner-scoped endpoints return 404 (not 403) for non-owners. |
422 | invalid_request | ValidationError | Body or query parameters failed validation. `field` points at the offending key when available. |
429 | rate_limit | RateLimitError | Per-tier limit hit. `Retry-After` header carries the cooldown in seconds. |
500 | internal_error | ApiError | Server-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:
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-Afterseconds (available aserr.retryAfterSec). - 5xx — exponential backoff with jitter. Always safe to retry idempotent methods (GET, DELETE).
- POST / PATCH — pass
Idempotency-Keyso a retry never creates a duplicate row. - 4xx (other) — do NOT retry blindly. Surface the error to the user.