subbydocs
Visit Subby Start building

Requests & responses

Base URLs, response envelope, IDs, amounts, dates, metadata, pagination, idempotency and versioning.

The Subby API is a REST API. It accepts JSON request bodies, returns JSON, and uses standard HTTP methods and status codes.

Base URLs

Environment Base URL
Live https://api.mysubbyapp.com/v1
Sandbox https://sandbox-api.mysubbyapp.com/v1

All requests must use HTTPS and include Content-Type: application/json when they have a body.

HTTP methods

Method Used for Metered
GET Retrieve one object or list objects Never
POST Create objects and perform actions (/pause, /cancel, /retry) Yes, when successful in live
PATCH Update some fields of an object Yes, when successful in live
DELETE Delete or archive an object Yes, when successful in live

The API does not use PUT. See API usage & metering.

Response envelope

Successful responses wrap the result in data:

{
  "success": true,
  "data": {
    "id": "sub_9fK3mQ2xLp",
    "object": "subscription",
    "status": "active",
    "livemode": true
  }
}

List responses return an array in data and pagination details in meta:

{
  "success": true,
  "data": [ { "id": "cus_1", "object": "customer" }, { "id": "cus_2", "object": "customer" } ],
  "meta": { "has_more": true, "next_cursor": "cus_2", "limit": 2 }
}

Errors return success: false and an error object. See Errors.

Object IDs

Every object has a string ID with a prefix that tells you its type:

Prefix Object
acct_ Account
plan_ Plan
cus_ Customer
sub_ Subscription
chg_ Charge
cs_ Checkout session
pm_ Payment method
ndg_ Nudge
we_ Webhook endpoint
evt_ Event
inv_ Subby invoice
clock_ Test clock (sandbox only)
req_ Request ID

Treat IDs as opaque strings of up to 64 characters. Don't parse them.

Amounts and currency

  • Amounts are integers in the smallest currency unit. For NGN that's kobo: 500000 means ₦5,000.00.
  • currency is a three-letter ISO 4217 code in upper case. NGN is supported today.
  • Never send decimals. 5000.00 is rejected with parameter_invalid.

Dates and times

All timestamps are ISO 8601 strings in UTC, for example 2026-09-17T14:02:11Z. Send timestamps in the same format.

Metadata

Plans, customers, subscriptions, charges and checkout sessions accept a metadata object for your own reference, such as your internal user ID.

  • Up to 20 keys.
  • Keys up to 40 characters, values up to 500 characters, strings only.
  • Set a key to "" in a PATCH to remove it.
  • Don't store sensitive data like card numbers or passwords in metadata.

You can filter list endpoints by one metadata key: GET /customers?metadata[your_user_id]=u_1024.

Pagination

List endpoints use cursor pagination.

Parameter Default Description
limit 20 Number of objects to return, from 1 to 100
starting_after — An object ID. Returns objects after this one
ending_before — An object ID. Returns objects before this one

Lists are sorted newest first. To fetch everything, pass meta.next_cursor as starting_after until has_more is false:

let cursor;
do {
  const url = new URL(`${process.env.SUBBY_BASE_URL}/subscriptions`);
  url.searchParams.set('limit', '100');
  if (cursor) url.searchParams.set('starting_after', cursor);

  const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.SUBBY_SECRET_KEY}` } });
  const { data, meta } = await res.json();
  for (const sub of data) handle(sub);
  cursor = meta.has_more ? meta.next_cursor : undefined;
} while (cursor);

Some fields hold the ID of a related object. Add expand[] to get the full object instead, which saves a request:

curl "https://api.mysubbyapp.com/v1/subscriptions/sub_9fK3mQ2xLp?expand[]=customer&expand[]=plan" \
  -H "Authorization: Bearer $SUBBY_SECRET_KEY"

You can expand up to 4 fields per request, one level deep.

Idempotency

Networks fail. If a POST times out, you can't tell whether Subby received it. Idempotency keys make retrying safe: send the same key again and you get the original response back instead of creating a duplicate.

curl "https://api.mysubbyapp.com/v1/subscriptions" \
  -H "Authorization: Bearer $SUBBY_SECRET_KEY" \
  -H "Idempotency-Key: 3b7f0d8e-1c2a-4e7b-9a55-0f5b3e1d9c21" \
  -H "Content-Type: application/json" \
  -d '{ "customer": "cus_4kQ", "plan": "plan_8Zp", "payment_method": "pm_2Hx" }'

How it works:

  • Send Idempotency-Key on any POST. A V4 UUID is a good choice. Maximum 255 characters.
  • Keys are stored for 24 hours per account per environment.
  • A replay returns the original status code and body, plus the header Idempotent-Replayed: true.
  • Replays are not metered.
  • Reusing a key with a different body returns 409 with idempotency_key_reused.
  • If the first request is still processing, a replay returns 409 with idempotency_request_in_progress. Retry after a moment.
  • Requests that failed validation (4xx) are not stored, so you can fix the body and retry with the same key.

PATCH and DELETE are naturally idempotent and don't need a key, though sending one does no harm.

Versioning

The current version is v1, in the URL path (/v1).

  • Backwards-compatible changes ship without a new version: new endpoints, new optional parameters, new fields in responses, new event types and new enum values. Build your integration to ignore fields and values it doesn't recognise.
  • Breaking changes only ship in a new version (/v2). We announce them at least 90 days in advance on the changelog and by email, and keep the old version running for at least 12 months after that.

Response headers

Header Description
X-Request-Id Unique ID for this request. Include it in support requests
X-Subby-Billable Whether this request counted toward your write allowance
X-Subby-Write-Usage / X-Subby-Write-Limit Current period write usage and allowance (write requests only)
RateLimit-Limit / RateLimit-Remaining / RateLimit-Reset See Rate limits
Idempotent-Replayed true when the response is a replay