Skip to content

Error Handling

The Spritz API uses RFC 9457 Problem Details for all error responses.

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"
}
]
}

Error types follow the pattern urn:problem-type:{category}:{specific-type}:

TypeStatusMeaning
auth:unauthorized401Missing or invalid credentials
auth:forbidden403Insufficient permissions
auth:token-expired401Token has expired
auth:invalid-token401Token is malformed or invalid
TypeStatusMeaning
validation:invalid-fields400Request body has invalid fields (see errors array)
validation:invalid-request400Request structure is invalid
TypeStatusMeaning
resource:not-found404Requested resource doesn’t exist
resource:conflict409Resource state conflict
TypeStatusMeaning
business:insufficient-funds422Insufficient balance for operation
business:verification-failed422Verification check failed
billpay:biller-not-found404Biller not found
billpay:has-pending-payments409Bill has pending payments, cannot delete
billpay:invalid-account-number400Account number format invalid for biller
billpay:payment-not-cancellable409Payment in non-cancellable state
card:pin-not-set404Card PIN not set
card:weak-pin400PIN too weak (sequential or repeating)
TypeStatusMeaning
system:internal-error500Unexpected server error
system:service-unavailable503Downstream service unavailable
system:database-error503Database error

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;
}
}

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)