@checkbox-site/node 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.js ADDED
@@ -0,0 +1,337 @@
1
+ // The Checkbox client: `const checkbox = new Checkbox(process.env.CHECKBOX_API_KEY)`.
2
+ // Zero runtime dependencies: global fetch (Node 20+, Bun, Deno, edge runtimes).
3
+ import { CheckboxAuthenticationError, CheckboxConnectionError, CheckboxError, CheckboxInvalidRequestError } from "./errors.js";
4
+ import * as errors from "./errors.js";
5
+ import { OPERATIONS, RESOURCES } from "./generated/operations.js";
6
+ import { API_VERSION } from "./generated/types.js";
7
+ import { listPromise } from "./resource.js";
8
+ import { SDK_VERSION } from "./version.js";
9
+ import { Webhooks, webhooks } from "./webhooks.js";
10
+ export const DEFAULT_BASE_URL = 'https://app.checkbox.site';
11
+ export const DEFAULT_TIMEOUT_MS = 80_000;
12
+ export const DEFAULT_MAX_NETWORK_RETRIES = 2;
13
+ const MAX_RETRY_DELAY_MS = 20_000;
14
+ /** `ck_test_…` / `rk_test_…` / `pk_test_…` -> test; `ck_live_…` / `rk_live_…` / `sk_live_…` / `pk_live_…` -> live. */
15
+ export function modeOfKey(key) {
16
+ const m = /^(?:ck|rk|sk|pk)_(test|live)_/.exec(key ?? '');
17
+ return m ? m[1] : undefined;
18
+ }
19
+ const env = (name) => {
20
+ const p = globalThis.process;
21
+ return p?.env?.[name] || undefined;
22
+ };
23
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
24
+ if (signal?.aborted)
25
+ return reject(signal.reason);
26
+ const t = setTimeout(resolve, ms);
27
+ signal?.addEventListener('abort', () => (clearTimeout(t), reject(signal.reason)), { once: true });
28
+ });
29
+ function newIdempotencyKey() {
30
+ const c = globalThis.crypto;
31
+ if (c?.randomUUID)
32
+ return `sdk_${c.randomUUID()}`;
33
+ return `sdk_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
34
+ }
35
+ function runtimeLabel() {
36
+ const g = globalThis;
37
+ if (g.process?.versions?.bun)
38
+ return `bun/${g.process.versions.bun}`;
39
+ if (g.Deno?.version?.deno)
40
+ return `deno/${g.Deno.version.deno}`;
41
+ if (g.process?.versions?.node)
42
+ return `node/${g.process.versions.node}`;
43
+ return 'js';
44
+ }
45
+ /**
46
+ * Checkbox API client.
47
+ *
48
+ * ```ts
49
+ * import Checkbox from '@checkbox-site/node';
50
+ * const checkbox = new Checkbox(process.env.CHECKBOX_API_KEY);
51
+ * const site = await checkbox.sites.create({ url: 'https://acme.example' });
52
+ * for await (const finding of checkbox.findings.list({ site: site.id })) console.log(finding.check_id, finding.status);
53
+ * ```
54
+ */
55
+ export class Checkbox {
56
+ static VERSION = SDK_VERSION;
57
+ static API_VERSION = API_VERSION;
58
+ static errors = errors;
59
+ static webhooks = webhooks;
60
+ static OPERATIONS = OPERATIONS;
61
+ static Webhooks = Webhooks;
62
+ /** Webhook signature verification: `checkbox.webhooks.constructEvent(rawBody, header, secret)`. */
63
+ webhooks = webhooks;
64
+ /** The API version sent as Checkbox-Version. */
65
+ apiVersion;
66
+ baseUrl;
67
+ /** 'test' for ck_test_/rk_test_ keys, 'live' for live keys, undefined without a (recognised) key. */
68
+ mode;
69
+ #apiKey;
70
+ #config;
71
+ #userAgent;
72
+ /**
73
+ * @param apiKey secret (`ck_test_…`, `ck_live_…`), restricted (`rk_…`) or legacy (`sk_live_…`) key. Defaults to
74
+ * the CHECKBOX_API_KEY environment variable, then CHECKBOX_SECRET_KEY (what `checkbox init` writes to .env.local).
75
+ */
76
+ constructor(apiKey, config = {}) {
77
+ this.#apiKey = (apiKey ?? env('CHECKBOX_API_KEY') ?? env('CHECKBOX_SECRET_KEY'))?.trim() || undefined;
78
+ this.#config = { maxNetworkRetries: DEFAULT_MAX_NETWORK_RETRIES, timeout: DEFAULT_TIMEOUT_MS, ...config };
79
+ this.apiVersion = config.apiVersion ?? API_VERSION;
80
+ this.baseUrl = (config.baseUrl ?? env('CHECKBOX_API_BASE') ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
81
+ this.mode = modeOfKey(this.#apiKey);
82
+ const app = config.appInfo ? ` ${config.appInfo.name}${config.appInfo.version ? `/${config.appInfo.version}` : ''}${config.appInfo.url ? ` (${config.appInfo.url})` : ''}` : '';
83
+ this.#userAgent = `checkbox-node/${SDK_VERSION} ${runtimeLabel()}${app}`;
84
+ for (const [ns, Cls] of Object.entries(RESOURCES)) {
85
+ Object.defineProperty(this, ns, { value: new Cls(this), enumerable: true });
86
+ }
87
+ }
88
+ /** True for live keys, false for test keys, undefined when unknown. */
89
+ get livemode() {
90
+ return this.mode === undefined ? undefined : this.mode === 'live';
91
+ }
92
+ /** Whether an API key is configured. */
93
+ get hasApiKey() {
94
+ return this.#apiKey !== undefined;
95
+ }
96
+ /**
97
+ * Calls any operation by its operationId with flat arguments (path parameters and params together). This is the
98
+ * shape MCP tools and agents use: `checkbox.call('retrieveSite', { site: 'site_…' })`.
99
+ */
100
+ call(operationId, args, options) {
101
+ const op = OPERATIONS[operationId];
102
+ if (!op) {
103
+ return Promise.reject(new CheckboxInvalidRequestError({ message: `Unknown operation "${String(operationId)}".`, type: 'invalid_request_error', code: 'route_not_found', param: 'operationId' }));
104
+ }
105
+ const rest = { ...(args ?? {}) };
106
+ const pathArgs = op.pathParams.map((name) => {
107
+ const v = rest[name];
108
+ delete rest[name];
109
+ return v;
110
+ });
111
+ return this._send(op, pathArgs, rest, options);
112
+ }
113
+ /** Like `call` for a paginated list operation, returning the auto-paginating list promise. */
114
+ callList(operationId, args, options) {
115
+ const op = OPERATIONS[operationId];
116
+ const rest = { ...(args ?? {}) };
117
+ const pathArgs = op.pathParams.map((name) => {
118
+ const v = rest[name];
119
+ delete rest[name];
120
+ return v;
121
+ });
122
+ return listPromise(this, op, pathArgs, rest, options);
123
+ }
124
+ /**
125
+ * Request to any API path with an explicit query and JSON body (escape hatch for endpoints newer than this SDK):
126
+ * `checkbox.request('POST', '/v1/new_thing', { query: { wait: 5 }, body: { a: 1 } })`. Retries, idempotency keys,
127
+ * versioning and typed errors apply as for the generated methods.
128
+ */
129
+ request(method, path, opts = {}) {
130
+ const { query, body, ...options } = opts;
131
+ return this.#raw(method, path, query ?? {}, body, options);
132
+ }
133
+ /**
134
+ * Stripe-style raw request: `params` go to the query string for GET/DELETE and to the JSON body for POST.
135
+ * `checkbox.rawRequest('GET', '/v1/sites', { limit: 3 })`.
136
+ */
137
+ rawRequest(method, path, params, options = {}) {
138
+ return (method === 'POST' ? this.#raw(method, path, {}, params ?? {}, options) : this.#raw(method, path, params ?? {}, undefined, options));
139
+ }
140
+ #raw(method, path, query, body, options) {
141
+ const op = {
142
+ operationId: 'request',
143
+ namespace: 'request',
144
+ method: 'request',
145
+ httpMethod: method,
146
+ path: path.startsWith('/') ? path : `/${path}`,
147
+ pathParams: [],
148
+ queryParams: Object.keys(query),
149
+ body: method === 'POST',
150
+ idempotent: method === 'POST',
151
+ auth: true,
152
+ scope: 'none',
153
+ response: 'json',
154
+ list: false,
155
+ paginated: false,
156
+ summary: '',
157
+ };
158
+ const overlap = Object.keys(query).filter((k) => body && k in body);
159
+ if (overlap.length) {
160
+ return Promise.reject(new CheckboxInvalidRequestError({ message: `"${overlap[0]}" is given both in query and body.`, type: 'invalid_request_error', code: 'parameter_invalid', param: overlap[0] }));
161
+ }
162
+ return this._send(op, [], { ...query, ...(body ?? {}) }, options);
163
+ }
164
+ /** @internal Used by the generated resources. */
165
+ async _send(op, pathArgs, params, options = {}) {
166
+ const apiKey = options.apiKey ?? this.#apiKey;
167
+ if (op.auth && !apiKey) {
168
+ throw new CheckboxAuthenticationError({
169
+ message: 'No API key provided. Pass it to new Checkbox(key) or set CHECKBOX_API_KEY (or CHECKBOX_SECRET_KEY, which `checkbox init` writes).',
170
+ type: 'authentication_error',
171
+ code: 'api_key_missing',
172
+ nextAction: {
173
+ type: 'get_api_key',
174
+ message: 'Get a free test key: POST /v1/device/authorize (one browser approval creates an account and test keys), the remote MCP server https://app.checkbox.site/mcp (setup_checkbox), or https://app.checkbox.site/settings/api-keys.',
175
+ url: 'https://app.checkbox.site/settings/api-keys',
176
+ },
177
+ });
178
+ }
179
+ let path = op.path;
180
+ op.pathParams.forEach((name, i) => {
181
+ const v = pathArgs[i];
182
+ if (typeof v !== 'string' || !v.trim()) {
183
+ throw new CheckboxInvalidRequestError({
184
+ message: `Missing required path parameter "${name}" for ${op.operationId}.`,
185
+ type: 'invalid_request_error',
186
+ code: 'parameter_missing',
187
+ param: name,
188
+ });
189
+ }
190
+ path = path.replace(`{${name}}`, encodeURIComponent(v));
191
+ });
192
+ const url = new URL(this.baseUrl + path);
193
+ const queryNames = new Set(op.queryParams);
194
+ const body = {};
195
+ for (const [k, v] of Object.entries(params ?? {})) {
196
+ if (v === undefined)
197
+ continue;
198
+ const inQuery = op.httpMethod !== 'POST' || queryNames.has(k) || (k === 'expand' && queryNames.has('expand[]'));
199
+ if (!inQuery) {
200
+ body[k] = v;
201
+ continue;
202
+ }
203
+ if (k === 'expand' || k === 'expand[]') {
204
+ for (const e of Array.isArray(v) ? v : [v])
205
+ url.searchParams.append('expand[]', String(e));
206
+ }
207
+ else if (Array.isArray(v)) {
208
+ for (const e of v)
209
+ url.searchParams.append(`${k}[]`, String(e));
210
+ }
211
+ else if (v !== null && typeof v === 'object') {
212
+ url.searchParams.set(k, JSON.stringify(v));
213
+ }
214
+ else {
215
+ url.searchParams.set(k, String(v));
216
+ }
217
+ }
218
+ const headers = {
219
+ accept: op.response === 'json' ? 'application/json' : '*/*',
220
+ 'checkbox-version': options.apiVersion ?? this.apiVersion,
221
+ 'user-agent': this.#userAgent,
222
+ ...(this.#config.headers ?? {}),
223
+ ...(options.headers ?? {}),
224
+ };
225
+ if (apiKey)
226
+ headers.authorization = `Bearer ${apiKey}`;
227
+ let payload;
228
+ if (op.httpMethod === 'POST') {
229
+ payload = JSON.stringify(body);
230
+ headers['content-type'] = 'application/json';
231
+ if (op.idempotent || options.idempotencyKey)
232
+ headers['idempotency-key'] = options.idempotencyKey ?? newIdempotencyKey();
233
+ }
234
+ const maxRetries = Math.max(0, options.maxNetworkRetries ?? this.#config.maxNetworkRetries);
235
+ const timeout = options.timeout ?? this.#config.timeout;
236
+ const f = this.#config.fetch ?? globalThis.fetch;
237
+ if (typeof f !== 'function')
238
+ throw new Error('No fetch implementation: use Node 20+ or pass { fetch } to new Checkbox().');
239
+ for (let attempt = 0;; attempt++) {
240
+ let res;
241
+ const signals = [AbortSignal.timeout(timeout), ...(options.signal ? [options.signal] : [])];
242
+ try {
243
+ res = await f(url, {
244
+ method: op.httpMethod,
245
+ headers,
246
+ ...(payload !== undefined ? { body: payload } : {}),
247
+ signal: signals.length > 1 ? AbortSignal.any(signals) : signals[0],
248
+ });
249
+ }
250
+ catch (err) {
251
+ if (options.signal?.aborted)
252
+ throw err;
253
+ if (attempt < maxRetries) {
254
+ await sleep(backoff(attempt), options.signal);
255
+ continue;
256
+ }
257
+ const timedOut = err?.name === 'TimeoutError';
258
+ throw new CheckboxConnectionError({
259
+ message: timedOut
260
+ ? `Request to ${url.origin} timed out after ${timeout} ms (${attempt + 1} attempt${attempt ? 's' : ''}).`
261
+ : `Could not reach ${url.origin}: ${err?.message ?? String(err)} (${attempt + 1} attempt${attempt ? 's' : ''}).`,
262
+ type: 'connection_error',
263
+ code: timedOut ? 'timeout' : 'connection_failed',
264
+ nextAction: { type: 'retry', message: 'Check the network and CHECKBOX_API_BASE, then retry; POSTs are safe to retry with the same idempotencyKey.' },
265
+ cause: err,
266
+ });
267
+ }
268
+ const resHeaders = {};
269
+ res.headers.forEach((v, k) => (resHeaders[k.toLowerCase()] = v));
270
+ if (!res.ok) {
271
+ const text = await res.text().catch(() => '');
272
+ let json;
273
+ try {
274
+ json = text ? JSON.parse(text) : undefined;
275
+ }
276
+ catch {
277
+ json = undefined;
278
+ }
279
+ const error = CheckboxError.generate(res.status, json ?? (text ? { error: text.slice(0, 300) } : undefined), resHeaders);
280
+ if (attempt < maxRetries && shouldRetry(res.status, error.code)) {
281
+ await sleep(retryDelay(attempt, resHeaders['retry-after']), options.signal);
282
+ continue;
283
+ }
284
+ throw error;
285
+ }
286
+ const meta = {
287
+ statusCode: res.status,
288
+ requestId: resHeaders['request-id'],
289
+ apiVersion: resHeaders['checkbox-version'],
290
+ idempotencyKey: headers['idempotency-key'],
291
+ idempotentReplayed: resHeaders['idempotent-replayed'] === 'true',
292
+ headers: resHeaders,
293
+ };
294
+ if (op.response === 'binary') {
295
+ const data = new Uint8Array(await res.arrayBuffer());
296
+ const disposition = resHeaders['content-disposition'] ?? '';
297
+ const file = { data, contentType: resHeaders['content-type'] ?? null, filename: /filename="?([^";]+)"?/i.exec(disposition)?.[1] ?? null };
298
+ return withMeta(file, meta);
299
+ }
300
+ const text = await res.text();
301
+ if (op.response === 'text')
302
+ return text;
303
+ let json;
304
+ try {
305
+ json = text ? JSON.parse(text) : {};
306
+ }
307
+ catch {
308
+ throw new CheckboxError({ message: `Invalid JSON from the API (HTTP ${res.status}).`, type: 'api_error', code: 'invalid_response', statusCode: res.status, requestId: meta.requestId, headers: resHeaders });
309
+ }
310
+ return withMeta(json, meta);
311
+ }
312
+ }
313
+ }
314
+ function withMeta(value, meta) {
315
+ if (value && typeof value === 'object' && Object.isExtensible(value)) {
316
+ Object.defineProperty(value, 'lastResponse', { value: meta, enumerable: false, configurable: true });
317
+ }
318
+ return value;
319
+ }
320
+ /** Response metadata of an object returned by the SDK (status, request id, idempotency replay, headers). */
321
+ export function getLastResponse(obj) {
322
+ return obj && typeof obj === 'object' ? (obj.lastResponse ?? undefined) : undefined;
323
+ }
324
+ function shouldRetry(status, code) {
325
+ return status === 429 || status >= 500 || (status === 409 && code === 'idempotency_key_in_use');
326
+ }
327
+ /** 0.5 s, 1 s, 2 s, ... capped at 20 s, with jitter (75-100 %). */
328
+ export function backoff(attempt) {
329
+ const base = Math.min(500 * 2 ** attempt, MAX_RETRY_DELAY_MS);
330
+ return Math.round(base * (0.75 + Math.random() * 0.25));
331
+ }
332
+ function retryDelay(attempt, retryAfter) {
333
+ const s = Number(retryAfter);
334
+ if (Number.isFinite(s) && s > 0)
335
+ return Math.min(s * 1000, MAX_RETRY_DELAY_MS);
336
+ return backoff(attempt);
337
+ }
@@ -0,0 +1,89 @@
1
+ /** `next_action` of an error or object: what an agent should do next. */
2
+ export interface NextAction {
3
+ /** Stable machine-readable action type, e.g. "verify_domain", "create_site", "wait". */
4
+ type: string;
5
+ /** One or two sentences for the agent or human. */
6
+ message: string;
7
+ /** API operation to call next (also the SDK method's operationId; `mcp_tool` names the MCP tool). */
8
+ operation_id?: string;
9
+ /** Page or document to open. */
10
+ url?: string;
11
+ [key: string]: unknown;
12
+ }
13
+ export type CheckboxErrorType = 'invalid_request_error' | 'authentication_error' | 'permission_error' | 'rate_limit_error' | 'idempotency_error' | 'api_error' | 'connection_error' | 'signature_verification_error';
14
+ export interface CheckboxErrorRaw {
15
+ type?: string;
16
+ code?: string;
17
+ message?: string;
18
+ param?: string;
19
+ doc_url?: string;
20
+ request_id?: string;
21
+ next_action?: NextAction;
22
+ [key: string]: unknown;
23
+ }
24
+ export interface CheckboxErrorInit {
25
+ message: string;
26
+ type: CheckboxErrorType;
27
+ code?: string | undefined;
28
+ param?: string | undefined;
29
+ statusCode?: number | undefined;
30
+ requestId?: string | undefined;
31
+ docUrl?: string | undefined;
32
+ nextAction?: NextAction | undefined;
33
+ headers?: Record<string, string> | undefined;
34
+ raw?: CheckboxErrorRaw | undefined;
35
+ cause?: unknown;
36
+ }
37
+ /** Base class of every error the SDK throws. Branch on `code` (stable) rather than on `message`. */
38
+ export declare class CheckboxError extends Error {
39
+ /** Error category (`invalid_request_error`, `authentication_error`, ...). */
40
+ readonly type: CheckboxErrorType;
41
+ /** Stable machine-readable code (`resource_missing`, `parameter_invalid`, ...); see ERROR_CODES. */
42
+ readonly code: string | undefined;
43
+ /** The parameter the error is about, when there is one. */
44
+ readonly param: string | undefined;
45
+ /** HTTP status (undefined for connection and signature errors). */
46
+ readonly statusCode: number | undefined;
47
+ /** `req_…` id; quote it to support. */
48
+ readonly requestId: string | undefined;
49
+ /** Documentation of the error code. */
50
+ readonly docUrl: string | undefined;
51
+ /** What an agent should do next (call an operation, open a URL, ask the human). */
52
+ readonly nextAction: NextAction | undefined;
53
+ /** Response headers (lower-case names). */
54
+ readonly headers: Record<string, string>;
55
+ /** The `error` object of the response body. */
56
+ readonly raw: CheckboxErrorRaw | undefined;
57
+ constructor(init: CheckboxErrorInit);
58
+ /** A JSON-friendly view (what an agent should log or show). */
59
+ toJSON(): Record<string, unknown>;
60
+ /** Builds the right subclass from an HTTP status and the response body's `error` object. */
61
+ static generate(statusCode: number, body: unknown, headers?: Record<string, string>): CheckboxError;
62
+ }
63
+ /** Bad parameters, unknown object (`resource_missing`), wrong state (`resource_conflict`), ... (HTTP 400/404/409/410). */
64
+ export declare class CheckboxInvalidRequestError extends CheckboxError {
65
+ }
66
+ /** Missing, unknown or revoked API key (HTTP 401). */
67
+ export declare class CheckboxAuthenticationError extends CheckboxError {
68
+ }
69
+ /** The key may not do this: publishable key, missing scope or role, live mode not activated (HTTP 403). */
70
+ export declare class CheckboxPermissionError extends CheckboxError {
71
+ }
72
+ /** Too many requests (HTTP 429); retried automatically, thrown when retries are exhausted. */
73
+ export declare class CheckboxRateLimitError extends CheckboxError {
74
+ }
75
+ /** An Idempotency-Key was reused with a different body, or is still in use. */
76
+ export declare class CheckboxIdempotencyError extends CheckboxError {
77
+ }
78
+ /** Something went wrong on Checkbox's side (HTTP 5xx); retrying with the same Idempotency-Key is safe. */
79
+ export declare class CheckboxAPIError extends CheckboxError {
80
+ }
81
+ /** The API could not be reached (DNS, TLS, timeout, connection reset) after the retries. */
82
+ export declare class CheckboxConnectionError extends CheckboxError {
83
+ }
84
+ /** A webhook's Checkbox-Signature did not match the payload and secret, or was too old. */
85
+ export declare class CheckboxSignatureVerificationError extends CheckboxError {
86
+ readonly header: string | undefined;
87
+ readonly payload: string;
88
+ constructor(message: string, header: string | undefined, payload: string);
89
+ }
package/dist/errors.js ADDED
@@ -0,0 +1,125 @@
1
+ // Typed errors (Stripe-style). Every API error carries the stable `code`, the `type`, the HTTP status, the request id and,
2
+ // for agents, `nextAction`: what to call or change next.
3
+ /** Base class of every error the SDK throws. Branch on `code` (stable) rather than on `message`. */
4
+ export class CheckboxError extends Error {
5
+ /** Error category (`invalid_request_error`, `authentication_error`, ...). */
6
+ type;
7
+ /** Stable machine-readable code (`resource_missing`, `parameter_invalid`, ...); see ERROR_CODES. */
8
+ code;
9
+ /** The parameter the error is about, when there is one. */
10
+ param;
11
+ /** HTTP status (undefined for connection and signature errors). */
12
+ statusCode;
13
+ /** `req_…` id; quote it to support. */
14
+ requestId;
15
+ /** Documentation of the error code. */
16
+ docUrl;
17
+ /** What an agent should do next (call an operation, open a URL, ask the human). */
18
+ nextAction;
19
+ /** Response headers (lower-case names). */
20
+ headers;
21
+ /** The `error` object of the response body. */
22
+ raw;
23
+ constructor(init) {
24
+ super(init.message, init.cause !== undefined ? { cause: init.cause } : undefined);
25
+ this.name = new.target.name;
26
+ this.type = init.type;
27
+ this.code = init.code;
28
+ this.param = init.param;
29
+ this.statusCode = init.statusCode;
30
+ this.requestId = init.requestId;
31
+ this.docUrl = init.docUrl;
32
+ this.nextAction = init.nextAction;
33
+ this.headers = init.headers ?? {};
34
+ this.raw = init.raw;
35
+ }
36
+ /** A JSON-friendly view (what an agent should log or show). */
37
+ toJSON() {
38
+ return {
39
+ name: this.name,
40
+ type: this.type,
41
+ code: this.code,
42
+ message: this.message,
43
+ param: this.param,
44
+ status: this.statusCode,
45
+ request_id: this.requestId,
46
+ doc_url: this.docUrl,
47
+ next_action: this.nextAction,
48
+ };
49
+ }
50
+ /** Builds the right subclass from an HTTP status and the response body's `error` object. */
51
+ static generate(statusCode, body, headers = {}) {
52
+ const e = body && typeof body === 'object' ? body.error : undefined;
53
+ const raw = e && typeof e === 'object' ? e : undefined;
54
+ const message = raw?.message ?? (typeof e === 'string' ? e : `Checkbox API returned HTTP ${statusCode}${statusCode === 404 ? ' (no such endpoint)' : ''}.`);
55
+ const type = raw?.type ?? typeForStatus(statusCode);
56
+ const init = {
57
+ message,
58
+ type,
59
+ code: raw?.code,
60
+ param: raw?.param,
61
+ statusCode,
62
+ requestId: raw?.request_id ?? headers['request-id'],
63
+ docUrl: raw?.doc_url,
64
+ nextAction: raw?.next_action && typeof raw.next_action === 'object' ? raw.next_action : undefined,
65
+ headers,
66
+ raw,
67
+ };
68
+ switch (type) {
69
+ case 'authentication_error':
70
+ return new CheckboxAuthenticationError(init);
71
+ case 'permission_error':
72
+ return new CheckboxPermissionError(init);
73
+ case 'rate_limit_error':
74
+ return new CheckboxRateLimitError(init);
75
+ case 'idempotency_error':
76
+ return new CheckboxIdempotencyError(init);
77
+ case 'invalid_request_error':
78
+ return new CheckboxInvalidRequestError(init);
79
+ default:
80
+ return new CheckboxAPIError(init);
81
+ }
82
+ }
83
+ }
84
+ function typeForStatus(status) {
85
+ if (status === 401)
86
+ return 'authentication_error';
87
+ if (status === 403)
88
+ return 'permission_error';
89
+ if (status === 429)
90
+ return 'rate_limit_error';
91
+ if (status >= 400 && status < 500)
92
+ return 'invalid_request_error';
93
+ return 'api_error';
94
+ }
95
+ /** Bad parameters, unknown object (`resource_missing`), wrong state (`resource_conflict`), ... (HTTP 400/404/409/410). */
96
+ export class CheckboxInvalidRequestError extends CheckboxError {
97
+ }
98
+ /** Missing, unknown or revoked API key (HTTP 401). */
99
+ export class CheckboxAuthenticationError extends CheckboxError {
100
+ }
101
+ /** The key may not do this: publishable key, missing scope or role, live mode not activated (HTTP 403). */
102
+ export class CheckboxPermissionError extends CheckboxError {
103
+ }
104
+ /** Too many requests (HTTP 429); retried automatically, thrown when retries are exhausted. */
105
+ export class CheckboxRateLimitError extends CheckboxError {
106
+ }
107
+ /** An Idempotency-Key was reused with a different body, or is still in use. */
108
+ export class CheckboxIdempotencyError extends CheckboxError {
109
+ }
110
+ /** Something went wrong on Checkbox's side (HTTP 5xx); retrying with the same Idempotency-Key is safe. */
111
+ export class CheckboxAPIError extends CheckboxError {
112
+ }
113
+ /** The API could not be reached (DNS, TLS, timeout, connection reset) after the retries. */
114
+ export class CheckboxConnectionError extends CheckboxError {
115
+ }
116
+ /** A webhook's Checkbox-Signature did not match the payload and secret, or was too old. */
117
+ export class CheckboxSignatureVerificationError extends CheckboxError {
118
+ header;
119
+ payload;
120
+ constructor(message, header, payload) {
121
+ super({ message, type: 'signature_verification_error', code: 'signature_verification_failed' });
122
+ this.header = header;
123
+ this.payload = payload;
124
+ }
125
+ }