Errors

Error handling, error codes, and error classes
View as Markdown

Error shape

All errors follow a consistent shape with UPPER_SNAKE_CASE error codes:

1import * as errors from "@gwop/sdk/models/errors";
2
3try {
4 await gwop.invoices.create({
5 body: { amountUsdc: -1 },
6 });
7} catch (err) {
8 if (err instanceof errors.ErrorResponse) {
9 console.log(err.data$.error.code); // "VALIDATION_ERROR"
10 console.log(err.data$.error.message); // "amount_usdc must be at least 1 (0.000001 USDC)"
11 console.log(err.statusCode); // 400
12 }
13}

Error codes

Build your error handling against codes, not HTTP status codes or message strings. Codes are stable across SDK versions.

CodeStatusMeaning
UNAUTHORIZED401Invalid, revoked, or missing API key
FORBIDDEN403Valid key but merchant account not active
VALIDATION_ERROR400Request body failed validation
INVOICE_NOT_FOUND404Invoice doesn’t exist or not visible to this merchant
INVOICE_CANCEL_NOT_ALLOWED400Cannot cancel — invoice is not OPEN
AUTH_INTENT_NOT_SETTLED402Agent hasn’t paid the auth challenge yet
AUTH_INTENT_NOT_FOUND404Auth intent doesn’t exist
SESSION_NOT_FOUND404Session doesn’t exist
IDEMPOTENCY_CONFLICT409Idempotency key reused with different parameters
RATE_LIMITED429Too many requests — check Retry-After header

Error classes

The SDK provides typed error classes:

1import * as errors from "@gwop/sdk/models/errors";
2
3try {
4 await gwop.invoices.create({ body: { amountUsdc: 1_000_000 } });
5} catch (err) {
6 if (err instanceof errors.ErrorResponse) {
7 // Most API errors: 400, 401, 403, 404, 409
8 err.data$.error.code; // UPPER_SNAKE_CASE error code
9 err.data$.error.message; // Human-readable description
10 err.statusCode; // HTTP status code
11 }
12
13 if (err instanceof errors.RateLimitError) {
14 // 429 Too Many Requests
15 err.data$.error.details?.retryAfterSeconds; // When to retry
16 }
17}

Common patterns

Retry on transient errors

1async function createInvoiceWithRetry(amount: number, maxRetries = 3) {
2 const idempotencyKey = crypto.randomUUID();
3
4 for (let attempt = 0; attempt < maxRetries; attempt++) {
5 try {
6 return await gwop.invoices.create({
7 idempotencyKey,
8 body: { amountUsdc: amount },
9 });
10 } catch (err) {
11 if (err instanceof errors.RateLimitError) {
12 const wait = err.data$.error.details?.retryAfterSeconds ?? 60;
13 await new Promise((r) => setTimeout(r, wait * 1000));
14 continue;
15 }
16 throw err; // Don't retry client errors
17 }
18 }
19 throw new Error("Max retries exceeded");
20}

Check for specific error codes

1try {
2 await gwop.authIntents.exchange({ authIntentId, idempotencyKey: crypto.randomUUID() });
3} catch (err) {
4 if (err instanceof errors.ErrorResponse) {
5 switch (err.data$.error.code) {
6 case "AUTH_INTENT_NOT_SETTLED":
7 // Agent hasn't paid yet — poll and retry
8 break;
9 case "AUTH_INTENT_NOT_FOUND":
10 // Invalid intent ID — create a new one
11 break;
12 default:
13 throw err;
14 }
15 }
16}