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

POST/auth/signupNo auth

Create 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": "..."
  }
}
POST/auth/loginNo auth

Authenticate with email and password. Returns a JWT.

Request body

{ "email": "you@example.com", "password": "..." }

Response

{ "accessToken": "eyJ...", "expiresIn": 604800, "user": { ... } }
POST/auth/logout

Invalidate the current session.

Response

{ "ok": true }
POST/auth/forgot-passwordNo auth

Sends a password reset email. Always returns 200 to prevent user enumeration.

Request body

{ "email": "you@example.com" }
POST/auth/reset-passwordNo auth

Reset the password using a token from the reset email.

Request body

{ "token": "...", "password": "newpassword" }

Response

{ "ok": true }

Stripe Connect

GET/stripe/connect

Get the Stripe OAuth URL to start the Connect flow. Redirect the user to the returned URL.

Response

{ "url": "https://connect.stripe.com/oauth/authorize?..." }
DELETE/stripe/connect

Revoke the Stripe OAuth token and disconnect the integration. Requires Owner or Admin role.

Response

{ "ok": true }
GET/stripe/health

Check the status of the connected Stripe integration.

Response

{
  "connected": true,
  "accountId": "acct_...",
  "hasCompatibilityIssue": false,
  "compatibilityMessage": null,
  "lastSyncedAt": "2026-03-06T12:00:00.000Z"
}

Payments

GET/payments

List 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 }
}
GET/payments/:id

Get full details of a single recovery case including all actions and audit entries.

POST/payments/:id/retry

Immediately enqueue a charge retry for a case. Requires Owner or Admin role.

Response

{ "ok": true, "actionId": "..." }
POST/payments/bulk

Apply 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

GET/dashboard/stats

Get high-level recovery statistics for your organisation.

Response

{
  "recoveredRevenue": 15000,
  "failedCount": 3,
  "recoveryRate": 0.857,
  "revenueAtRisk": 2000,
  "currency": "usd"
}

Settings & Billing

GET/settings/profile

Get the current user and organisation profile.

PUT/settings/profile

Update name or organisation name. Requires Owner or Admin.

Request body

{ "name": "Jane", "orgName": "Acme Corp" }

Response

{ "ok": true }
GET/settings/retry-rules

Get custom retry rule overrides (Scale plan only).

PUT/settings/retry-rules

Update retry rule overrides per decline code (Scale plan only).

Requires Scale plan.

Request body

{
  "overrides": {
    "insufficient_funds": { "retryAfterHours": 12, "enabled": true }
  }
}

Response

{ "ok": true }
GET/settings/notifications

Get current notification settings.

PUT/settings/notifications

Update 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 }
GET/settings/billing

Get billing status, plan, and usage for the current period.

Response

{
  "plan": "STARTER",
  "status": "ACTIVE",
  "trialEndsAt": null,
  "currentPeriodEnd": null,
  "cancelAtPeriodEnd": false,
  "usageCurrent": 5,
  "usageLimit": 100
}
POST/billing/checkout

Create a Stripe Checkout session to upgrade your plan.

Request body

{ "priceId": "price_..." }

Response

{ "url": "https://checkout.stripe.com/..." }
GET/billing/portal

Get a Stripe billing portal URL to manage subscription and invoices.

Response

{ "url": "https://billing.stripe.com/..." }

Error codes

CodeMeaning
VALIDATION_ERRORRequest body failed Zod schema validation.
UNAUTHORIZEDMissing or invalid JWT token.
FORBIDDENAuthenticated but insufficient permissions.
NOT_FOUNDRequested resource does not exist.
ALREADY_RECOVEREDRecovery case is already in a terminal state.
EMAIL_TAKENEmail address is already registered.
INVALID_CREDENTIALSEmail or password is incorrect.
STRIPE_WEBHOOK_INVALID_SIGNATUREWebhook signature verification failed.
PLAN_REQUIREDFeature requires a higher plan tier.
NO_STRIPE_INTEGRATIONNo connected Stripe account found for this organisation.