Errors
Error format, status codes, and rate limits
All errors are returned as JSON with a consistent shape.
Error format
{
"statusCode": 401,
"message": "Unauthorized"
}message is a string for a single error. When validation fails it can be an array of
strings:
{
"statusCode": 400,
"message": ["displayName must be shorter than or equal to 64 characters"]
}Status codes
| Status | Meaning |
|---|---|
200 | Success |
204 | Success, no content (e.g. POST /v1/auth/refresh) |
302 | Redirect (Google OAuth flow) |
400 | Validation error — one or more fields are invalid |
401 | Missing, expired, or invalid access/refresh token |
403 | Authenticated but forbidden — e.g. withdrawals disabled, admin-only route, revoked grant |
404 | Identity, profile, or wallet does not exist |
409 | Conflict — e.g. email already associated with another account, PID already taken, or credential already linked |
410 | Gone — claim ticket expired; sign in again for a fresh one |
429 | Rate limit exceeded |
503 | Google OAuth is not configured on the server |
Authentication errors
A 401 from a protected endpoint means the access token is missing or expired. Recover by
calling POST /v1/auth/refresh. If refresh itself returns 401, the session is
unrecoverable and the user must sign in again.
Rate limits
Auth endpoints are rate limited to protect against brute force:
| Endpoint | Limit |
|---|---|
POST /v1/auth/refresh | 10 requests / minute |
POST /v1/auth/login | 20 requests / minute |
POST /v1/auth/logout | 20 requests / minute |
POST /v1/auth/exchange | 20 requests / minute |
POST /v1/auth/authorize | 20 requests / minute |
POST /v1/apps | 10 requests / minute |
GET /v1/apps | 30 requests / minute |
PATCH /v1/apps/:id | 20 requests / minute |
POST /v1/apps/:id/secret | 5 requests / minute |
POST /v1/apps/:id/webhook | 5 requests / minute |
GET /v1/apps/:id/fees | 30 requests / minute |
PUT /v1/apps/:id/fees/:operation | 20 requests / minute |
POST /v1/auth/token | 20 requests / minute |
POST /v1/fiat/deposits/checkout | 10 requests / minute |
POST /v1/fiat/transfers/inquiry, …/confirm | 10 requests / minute |
POST /v1/fiat/deposits/:id/sync | 12 requests / minute |
GET /v1/auth/google, /v1/auth/google/callback | 100 requests / minute |
POST /v1/wallet | 10 requests / minute |
Exceeding a limit returns 429. Clients should back off and retry.
Fiat errors
Fiat endpoints return the same JSON shape, but the SDK fiat methods throw
(a plain Error whose message is the server message) instead of returning the
T | ApiError union that auth/wallet reads use. Handle them with try/catch:
try {
await peridot.fiat.transferConfirm(inquiryId);
} catch (e) {
// e.message: "Insufficient internal credit", "Amount too small…", etc.
}Common fiat statuses: 400 (bad amount / net too small), 401 (no session),
404 (unknown recipient / transaction), 503 (ledger disabled or frozen).
SDK popup errors
Trust-critical SDK calls in popup mode throw instead of returning unions:
| Error | Meaning |
|---|---|
PopupBlockedError | Browser blocked the popup — ask the user to allow popups, then retry |
PopupClosedError | User closed the window, or it timed out (login 5 min, approval 2 min) |
PopupUnavailableError | No popupBaseUrl set, or not running in a browser |
Error("…rejected…") | User pressed Deny, or the host rejected a malformed/tampered request |
A closed or timed-out popup never resolves — treat it as rejected.