Developers
API Reference
The Savby REST API lets you integrate recovery data into your own systems or build custom workflows on top of Savby.
Base URL & authentication
Base URL: https://api.savby.io/api/v1
# All protected endpoints require:
Authorization: Bearer <accessToken>Obtain an accessToken from POST /auth/login or POST /auth/signup. Tokens expire after 7 days.
Error format
All error responses follow a consistent envelope:
{
"error": {
"code": "NOT_FOUND",
"message": "Recovery case not found",
"requestId": "req_01abc..."
}
}Authentication
/auth/signupNo authCreate a new account and organisation.
Request body
{
"email": "you@example.com",
"password": "min8chars",
"name": "Jane Smith" // optional
}Response
{
"accessToken": "eyJ...",
"expiresIn": 604800,
"user": {
"id": "...", "email": "...", "name": "...",
"role": "OWNER", "orgId": "..."
}
}/auth/loginNo authAuthenticate with email and password. Returns a JWT.
Request body
{ "email": "you@example.com", "password": "..." }Response
{ "accessToken": "eyJ...", "expiresIn": 604800, "user": { ... } }/auth/logoutInvalidate the current session.
Response
{ "ok": true }/auth/forgot-passwordNo authSends a password reset email. Always returns 200 to prevent user enumeration.
Request body
{ "email": "you@example.com" }/auth/reset-passwordNo authReset the password using a token from the reset email.
Request body
{ "token": "...", "password": "newpassword" }Response
{ "ok": true }Stripe Connect
/stripe/connectGet the Stripe OAuth URL to start the Connect flow. Redirect the user to the returned URL.
Response
{ "url": "https://connect.stripe.com/oauth/authorize?..." }/stripe/connectRevoke the Stripe OAuth token and disconnect the integration. Requires Owner or Admin role.
Response
{ "ok": true }/stripe/healthCheck the status of the connected Stripe integration.
Response
{
"connected": true,
"accountId": "acct_...",
"hasCompatibilityIssue": false,
"compatibilityMessage": null,
"lastSyncedAt": "2026-03-06T12:00:00.000Z"
}Payments
/paymentsList recovery cases for your organisation with pagination.
Query params: status (OPEN|RUNNING|PAUSED|RECOVERED|EXHAUSTED), search, from, to, page (default 1), pageSize (default 25, max 100)
Response
{
"data": [{
"id": "...",
"status": "RUNNING",
"amountDue": 5000,
"amountRecovered": 0,
"currency": "usd",
"declineCode": "card_declined",
"createdAt": "...",
"customer": { "id": "...", "email": "...", "name": "..." },
"invoice": { "id": "...", "stripeInvoiceId": "in_...", "status": "..." },
"nextAction": { "type": "RETRY_CHARGE", "scheduledFor": "..." }
}],
"pagination": { "page": 1, "pageSize": 25, "total": 42, "totalPages": 2 }
}/payments/:idGet full details of a single recovery case including all actions and audit entries.
/payments/:id/retryImmediately enqueue a charge retry for a case. Requires Owner or Admin role.
Response
{ "ok": true, "actionId": "..." }/payments/bulkApply an action to multiple cases at once. Requires Owner or Admin role.
Request body
{
"caseIds": ["caseId1", "caseId2"],
"action": "retry" // "retry" or "dismiss"
}Response
{ "ok": true, "count": 2 }Dashboard
/dashboard/statsGet high-level recovery statistics for your organisation.
Response
{
"recoveredRevenue": 15000,
"failedCount": 3,
"recoveryRate": 0.857,
"revenueAtRisk": 2000,
"currency": "usd"
}Settings & Billing
/settings/profileGet the current user and organisation profile.
/settings/profileUpdate name or organisation name. Requires Owner or Admin.
Request body
{ "name": "Jane", "orgName": "Acme Corp" }Response
{ "ok": true }/settings/retry-rulesGet custom retry rule overrides (Scale plan only).
/settings/retry-rulesUpdate retry rule overrides per decline code (Scale plan only).
Requires Scale plan.
Request body
{
"overrides": {
"insufficient_funds": { "retryAfterHours": 12, "enabled": true }
}
}Response
{ "ok": true }/settings/notificationsGet current notification settings.
/settings/notificationsUpdate email and Slack notification preferences.
Request body
{
"email": {
"enabled": true,
"onRecovery": true,
"onExhausted": true,
"dailySummary": false,
"revenueAtRiskThreshold": 1000
},
"slack": {
"enabled": true,
"webhookUrl": "https://hooks.slack.com/...",
"onRecovery": true,
"onExhausted": true
}
}Response
{ "ok": true }/settings/billingGet billing status, plan, and usage for the current period.
Response
{
"plan": "STARTER",
"status": "ACTIVE",
"trialEndsAt": null,
"currentPeriodEnd": null,
"cancelAtPeriodEnd": false,
"usageCurrent": 5,
"usageLimit": 100
}/billing/checkoutCreate a Stripe Checkout session to upgrade your plan.
Request body
{ "priceId": "price_..." }Response
{ "url": "https://checkout.stripe.com/..." }/billing/portalGet a Stripe billing portal URL to manage subscription and invoices.
Response
{ "url": "https://billing.stripe.com/..." }Error codes
| Code | Meaning |
|---|---|
| VALIDATION_ERROR | Request body failed Zod schema validation. |
| UNAUTHORIZED | Missing or invalid JWT token. |
| FORBIDDEN | Authenticated but insufficient permissions. |
| NOT_FOUND | Requested resource does not exist. |
| ALREADY_RECOVERED | Recovery case is already in a terminal state. |
| EMAIL_TAKEN | Email address is already registered. |
| INVALID_CREDENTIALS | Email or password is incorrect. |
| STRIPE_WEBHOOK_INVALID_SIGNATURE | Webhook signature verification failed. |
| PLAN_REQUIRED | Feature requires a higher plan tier. |
| NO_STRIPE_INTEGRATION | No connected Stripe account found for this organisation. |