Error Handling
The Spritz API uses RFC 9457 Problem Details for all error responses.
Error response format
Section titled “Error response format”All errors are returned with Content-Type: application/problem+json:
{ "type": "urn:problem-type:validation:invalid-fields", "title": "Validation Error", "status": 400, "detail": "One or more validation errors occurred.", "instance": "/errors/1710000000000", "errors": [ { "field": "amount", "message": "Amount must be a positive number", "code": "INVALID_INPUT" } ]}Problem type URNs
Section titled “Problem type URNs”Error types follow the pattern urn:problem-type:{category}:{specific-type}:
Authentication & authorization
Section titled “Authentication & authorization”| Type | Status | Meaning |
|---|---|---|
auth:unauthorized | 401 | Missing or invalid credentials |
auth:forbidden | 403 | Insufficient permissions |
auth:token-expired | 401 | Token has expired |
auth:invalid-token | 401 | Token is malformed or invalid |
Validation
Section titled “Validation”| Type | Status | Meaning |
|---|---|---|
validation:invalid-fields | 400 | Request body has invalid fields (see errors array) |
validation:invalid-request | 400 | Request structure is invalid |
Resources
Section titled “Resources”| Type | Status | Meaning |
|---|---|---|
resource:not-found | 404 | Requested resource doesn’t exist |
resource:conflict | 409 | Resource state conflict |
Business logic
Section titled “Business logic”| Type | Status | Meaning |
|---|---|---|
business:insufficient-funds | 422 | Insufficient balance for operation |
business:verification-failed | 422 | Verification check failed |
billpay:biller-not-found | 404 | Biller not found |
billpay:has-pending-payments | 409 | Bill has pending payments, cannot delete |
billpay:invalid-account-number | 400 | Account number format invalid for biller |
billpay:payment-not-cancellable | 409 | Payment in non-cancellable state |
card:pin-not-set | 404 | Card PIN not set |
card:weak-pin | 400 | PIN too weak (sequential or repeating) |
System
Section titled “System”| Type | Status | Meaning |
|---|---|---|
system:internal-error | 500 | Unexpected server error |
system:service-unavailable | 503 | Downstream service unavailable |
system:database-error | 503 | Database error |
Handling errors
Section titled “Handling errors”Match on the status code for broad handling, or on the type field for specific recovery:
const res = await fetch(`${API_URL}/v1/off-ramp-quotes`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, body: JSON.stringify({ amount: "100.00", currency: "USD" }),});
if (!res.ok) { const problem = await res.json();
switch (problem.status) { case 400: // Validation error — check problem.errors for field-level details console.error("Validation:", problem.errors); break; case 401: // Auth error — refresh token or re-authenticate break; case 404: // Resource not found break; case 429: // Rate limited — retry after delay const retryAfter = res.headers.get("Retry-After"); break; default: // Server error — retry with backoff break; }}Timeouts
Section titled “Timeouts”The API enforces a 30-second request timeout. Requests exceeding this return a 504 Gateway Timeout.
Prompt for your LLM Copy this into Claude, Cursor, or your AI assistant
You are handling errors from the Spritz Finance API.
Error format: RFC 9457 Problem Details
Content-Type: application/problem+json
Response shape:
{
"type": "urn:problem-type:<category>:<specific>",
"title": "Human-readable title",
"status": <http-status>,
"detail": "Specific error description",
"instance": "/errors/<id>"
}
Validation errors include an "errors" array:
{
"type": "urn:problem-type:validation:invalid-fields",
"status": 400,
"errors": [{ "field": "amount", "message": "Required", "code": "INVALID_INPUT" }]
}
Status code mapping:
- 400 = validation errors (check "errors" array for field details)
- 401 = authentication errors (token missing, expired, or invalid)
- 403 = authorization errors (insufficient permissions)
- 404 = resource not found (check "resourceType" and "resourceId" fields)
- 409 = conflict (e.g. bill has pending payments)
- 422 = business logic errors (insufficient funds, verification failed)
- 429 = rate limited (check Retry-After header)
- 500 = internal server error (retry with exponential backoff)
- 503 = service/database unavailable (retry with backoff)
- 504 = timeout (request exceeded 30s limit)
Error type URN categories: auth, validation, resource, business, billpay, card, system
Implementation pattern (TypeScript):
```typescript
class SpritzApiError extends Error {
constructor(
public readonly problem: {
type: string;
title: string;
status: number;
detail?: string;
errors?: Array<{ field: string; message: string; code?: string }>;
}
) {
super(`${problem.title}: ${problem.detail ?? "Unknown error"}`);
this.name = "SpritzApiError";
}
get isRetryable() {
return this.problem.status >= 500 || this.problem.status === 429;
}
}
async function spritzFetch(url: string, init?: RequestInit) {
const res = await fetch(url, init);
if (!res.ok) {
const problem = await res.json();
throw new SpritzApiError(problem);
}
return res.json();
}
```
Retry strategy:
- 429: respect Retry-After header
- 500/503/504: exponential backoff (1s, 2s, 4s), max 3 retries
- 400/401/403/404/409/422: do NOT retry (client errors)