Errors
Error handling, error codes, and error classes
Error shape
All errors follow a consistent shape with UPPER_SNAKE_CASE error codes:
1 import * as errors from "@gwop/sdk/models/errors"; 2 3 try { 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.
| Code | Status | Meaning |
|---|---|---|
UNAUTHORIZED | 401 | Invalid, revoked, or missing API key |
FORBIDDEN | 403 | Valid key but merchant account not active |
VALIDATION_ERROR | 400 | Request body failed validation |
INVOICE_NOT_FOUND | 404 | Invoice doesn’t exist or not visible to this merchant |
INVOICE_CANCEL_NOT_ALLOWED | 400 | Cannot cancel — invoice is not OPEN |
AUTH_INTENT_NOT_SETTLED | 402 | Agent hasn’t paid the auth challenge yet |
AUTH_INTENT_NOT_FOUND | 404 | Auth intent doesn’t exist |
SESSION_NOT_FOUND | 404 | Session doesn’t exist |
IDEMPOTENCY_CONFLICT | 409 | Idempotency key reused with different parameters |
RATE_LIMITED | 429 | Too many requests — check Retry-After header |
Error classes
The SDK provides typed error classes:
1 import * as errors from "@gwop/sdk/models/errors"; 2 3 try { 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
1 async 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
1 try { 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 }