openapi: 3.0.3
info:
title: 'SaasBilling API Documentation'
description: 'SaasBilling provides a single REST API to manage subscribers, subscriptions, plans, entitlements, metered usage and invoices on top of Stripe. The subscriber portal is hosted by SaasBilling and the actual payment method is collected by Stripe. Authenticate with a project API key and call the v1 endpoints below.'
version: 1.0.0
servers:
- url: 'https://saasbilling.io'
tags:
- name: Subscribers
description: 'Manage subscribers in your project'
- name: Subscriptions
description: 'Subscription lifecycle and listing'
- name: Plans
description: 'List and retrieve plans in your project'
- name: Portal
description: 'Whitelabel billing portal sessions'
- name: Entitlements
description: 'Resolve subscriber feature entitlements'
- name: Usage
description: 'Metered usage records and summaries'
- name: Invoices
description: 'List and retrieve invoices'
- name: 'Webhook Endpoints'
description: 'Manage outbound webhook endpoints and discover subscribable events'
- name: Health
description: 'Liveness probe for the public API'
components:
securitySchemes:
default:
type: http
scheme: bearer
description: 'Send the project API key as Bearer sb_test_… or Bearer sb_live_…, or use the X-Project-Api-Key header with the same value.'
security:
-
default: []
paths:
/api/v1/subscribers:
get:
summary: 'List subscribers'
operationId: listSubscribers
description: "Returns the most recent subscribers in your project, newest first, with\nStripe-style cursor pagination: pass the last item's `id` as\n`starting_after` to fetch the next page, until `has_more` is `false`.\nUse the `email` filter to look one up by address, or `type` to narrow\nthe list to users or organizations."
parameters:
-
in: query
name: type
description: 'Filter by subscriber type ("user" or "organization").'
example: organization
required: false
schema:
type: string
description: 'Filter by subscriber type ("user" or "organization").'
example: organization
-
in: query
name: email
description: 'Filter by exact email address.'
example: user@example.com
required: false
schema:
type: string
description: 'Filter by exact email address.'
example: user@example.com
-
in: query
name: limit
description: 'Page size, 1-100. Defaults to 100.'
example: 25
required: false
schema:
type: integer
description: 'Page size, 1-100. Defaults to 100.'
example: 25
-
in: query
name: starting_after
description: 'Cursor: the `id` of the last subscriber on the previous page.'
example: sbr_01HXYZ
required: false
schema:
type: string
description: 'Cursor: the `id` of the last subscriber on the previous page.'
example: sbr_01HXYZ
responses:
200:
description: 'List wrapper. `data[]` items follow the Subscriber resource shape; `has_more` signals another page behind the `starting_after` cursor.'
content:
application/json:
schema:
type: object
example:
object: list
has_more: false
data:
-
object: subscriber
id: sbr_01HXYZ
external_id: user_12345
type: user
email: user@example.com
name: 'John Doe'
metadata: []
created_at: '2026-05-20T10:00:00+00:00'
updated_at: '2026-05-20T10:00:00+00:00'
properties:
object:
type: string
example: list
has_more:
type: boolean
example: false
data:
type: array
example:
-
object: subscriber
id: sbr_01HXYZ
external_id: user_12345
type: user
email: user@example.com
name: 'John Doe'
metadata: []
created_at: '2026-05-20T10:00:00+00:00'
updated_at: '2026-05-20T10:00:00+00:00'
items:
type: object
properties:
object:
type: string
example: subscriber
id:
type: string
example: sbr_01HXYZ
external_id:
type: string
example: user_12345
type:
type: string
example: user
email:
type: string
example: user@example.com
name:
type: string
example: 'John Doe'
metadata:
type: array
example: []
created_at:
type: string
example: '2026-05-20T10:00:00+00:00'
updated_at:
type: string
example: '2026-05-20T10:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscribers:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscribers:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscribers:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
tags:
- Subscribers
post:
summary: 'Create or update a subscriber'
operationId: createOrUpdateASubscriber
description: "Idempotently upserts a subscriber by `external_id`. Call this right after\na customer signs up in your application — SaasBilling will create the\nsubscriber on Stripe if needed and mirror the identity locally. Pass an\n`Idempotency-Key` header to safely retry on network errors."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: subscriber
id: sbr_ic0ln24bcbmpbhdbaffl
external_id: usr_6ma3t5jmdncx
type: user
email: wleuschke@example.net
name: 'Mina Bauch'
metadata: {}
created_at: '2026-10-22T07:20:10+00:00'
updated_at: '2026-10-22T07:20:10+00:00'
properties:
object:
type: string
example: subscriber
id:
type: string
example: sbr_ic0ln24bcbmpbhdbaffl
external_id:
type: string
example: usr_6ma3t5jmdncx
type:
type: string
example: user
email:
type: string
example: wleuschke@example.net
name:
type: string
example: 'Mina Bauch'
metadata:
type: object
properties: {}
created_at:
type: string
example: '2026-10-22T07:20:10+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:10+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscribers:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscribers:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscribers:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
tags:
- Subscribers
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
external_id:
type: string
description: 'Your own identifier for the user or tenant this subscriber represents — whatever primary id you already use in your application. SaasBilling stores it as an opaque string with no required format or prefix (allowed characters: letters, digits and `_ . - :`). The `user_12345` example is illustrative only; a bare `42` or a UUID work just as well.'
example: user_12345
type:
type: string
description: 'Subscriber type: "user" or "organization".'
example: user
email:
type: string
description: 'Email address of the subscriber.'
example: user@example.com
name:
type: string
description: 'Display name of the subscriber.'
example: 'John Doe'
currency:
type: string
description: "ISO 4217 code of the currency this subscriber is billed in (must be one of the project's enabled currencies). The whitelabel portal prices every plan in this currency for the subscriber — the end user cannot switch it. Omitting the field leaves an already stored currency untouched (a new subscriber is created without one); send null to clear it and inherit the project default currency."
example: EUR
locale:
type: string
description: "Language the whitelabel portal is rendered in for this subscriber (must be one of the project's attached locales). Set it to whatever language your own application already serves this customer in. Omitting the field leaves an already stored locale untouched (a new subscriber is created without one); send null to clear it and inherit the project default locale."
example: fr
metadata:
type: object
description: 'Arbitrary key-value metadata.'
example:
plan: premium
properties: {}
required:
- external_id
- type
'/api/v1/subscribers/{external_id}':
get:
summary: 'Retrieve a subscriber'
operationId: retrieveASubscriber
description: "Returns the subscriber identified by your application's `external_id`.\nUseful to verify SaasBilling has the latest profile information or to fetch\nthe linked Stripe customer id."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: subscriber
id: sbr_eusckdwi9h5zzvd7xksq
external_id: usr_x1dbyd2jdu1k
type: user
email: price.amber@example.org
name: 'Miss Jazlyn Keebler III'
metadata: {}
created_at: '2026-10-22T07:20:10+00:00'
updated_at: '2026-10-22T07:20:10+00:00'
properties:
object:
type: string
example: subscriber
id:
type: string
example: sbr_eusckdwi9h5zzvd7xksq
external_id:
type: string
example: usr_x1dbyd2jdu1k
type:
type: string
example: user
email:
type: string
example: price.amber@example.org
name:
type: string
example: 'Miss Jazlyn Keebler III'
metadata:
type: object
properties: {}
created_at:
type: string
example: '2026-10-22T07:20:10+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:10+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscribers:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscribers:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscribers:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Subscribers
patch:
summary: 'Update a subscriber'
operationId: updateASubscriber
description: "Partially updates a subscriber. Only the fields you send are changed;\nexisting email, type, name and metadata are preserved. Stripe is\nreconciled automatically."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: subscriber
id: sbr_rwnwgjxhifrs3elkbons
external_id: usr_ht2pxe6uxvqo
type: user
email: ferne52@example.com
name: 'Mr. Carey Smitham'
metadata: {}
created_at: '2026-10-22T07:20:10+00:00'
updated_at: '2026-10-22T07:20:10+00:00'
properties:
object:
type: string
example: subscriber
id:
type: string
example: sbr_rwnwgjxhifrs3elkbons
external_id:
type: string
example: usr_ht2pxe6uxvqo
type:
type: string
example: user
email:
type: string
example: ferne52@example.com
name:
type: string
example: 'Mr. Carey Smitham'
metadata:
type: object
properties: {}
created_at:
type: string
example: '2026-10-22T07:20:10+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:10+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscribers:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscribers:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscribers:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Subscribers
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
type:
type: string
description: 'Subscriber type: "user" or "organization".'
example: organization
email:
type: string
description: 'New email address.'
example: newemail@example.com
name:
type: string
description: 'New display name.'
example: 'Jane Doe'
currency:
type: string
description: "ISO 4217 billing currency code (one of the project's enabled currencies), or null to inherit the project default. Drives the portal pricing currency for this subscriber."
example: EUR
locale:
type: string
description: "Portal language code (one of the project's attached locales), or null to inherit the project default locale."
example: fr
metadata:
type: object
description: 'Updated metadata (merge).'
example:
plan: basic
properties: {}
delete:
summary: 'Delete a subscriber'
operationId: deleteASubscriber
description: "Soft-deletes a subscriber. Active subscriptions are cancelled at the\nperiod end by Stripe; the local row is kept for historic invoice access."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
204:
description: 'Subscriber soft-deleted.'
content:
application/json:
schema:
type: object
nullable: true
403:
description: 'Forbidden — API key lacks the required scope `subscribers:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscribers:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscribers:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Subscribers
parameters:
-
in: path
name: external_id
description: 'The external ID of the subscriber.'
example: architecto
required: true
schema:
type: string
'/api/v1/subscribers/{external_id}/subscriptions':
get:
summary: "List a subscriber's subscriptions"
operationId: listASubscribersSubscriptions
description: "Returns every subscription for the subscriber, active ones first. Use\nthis to display a billing history page or to find the subscription id\nto cancel, swap or resume."
parameters: []
responses:
200:
description: "List of the subscriber's subscriptions, active subscriptions first. `data[]` items follow the Subscription resource shape."
content:
application/json:
schema:
type: object
example:
object: list
data:
-
object: subscription
id: sub_01HXYZ
project_id: '1'
subscriber_id: '1'
plan_id: '1'
status: active
current_period_start: '2026-05-01T00:00:00+00:00'
current_period_end: '2026-06-01T00:00:00+00:00'
trial_ends_at: null
cancel_at: null
canceled_at: null
seats: 5
included_seats: 3
metadata: []
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-01T00:00:00+00:00'
properties:
object:
type: string
example: list
data:
type: array
example:
-
object: subscription
id: sub_01HXYZ
project_id: '1'
subscriber_id: '1'
plan_id: '1'
status: active
current_period_start: '2026-05-01T00:00:00+00:00'
current_period_end: '2026-06-01T00:00:00+00:00'
trial_ends_at: null
cancel_at: null
canceled_at: null
seats: 5
included_seats: 3
metadata: []
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-01T00:00:00+00:00'
items:
type: object
properties:
object:
type: string
example: subscription
id:
type: string
example: sub_01HXYZ
project_id:
type: string
example: '1'
subscriber_id:
type: string
example: '1'
plan_id:
type: string
example: '1'
status:
type: string
example: active
current_period_start:
type: string
example: '2026-05-01T00:00:00+00:00'
current_period_end:
type: string
example: '2026-06-01T00:00:00+00:00'
trial_ends_at:
type: string
example: null
nullable: true
cancel_at:
type: string
example: null
nullable: true
canceled_at:
type: string
example: null
nullable: true
seats:
type: integer
example: 5
included_seats:
type: integer
example: 3
metadata:
type: array
example: []
created_at:
type: string
example: '2026-05-01T00:00:00+00:00'
updated_at:
type: string
example: '2026-05-01T00:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscriptions:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscriptions:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscriptions:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Subscriptions
parameters:
-
in: path
name: external_id
description: 'External identifier of the subscriber.'
example: user_12345
required: true
schema:
type: string
'/api/v1/subscriptions/{id}':
get:
summary: 'Retrieve a subscription'
operationId: retrieveASubscription
description: "Returns the subscription identified by its public id (`sub_*`) or numeric\nid. Includes the resolved plan, prices and current billing period bounds."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: subscription
id: sub_8xkorpcbz0llchq98drq
project_id: '84'
subscriber_id: '44'
plan_id: '29'
status: active
current_period_start: '2026-10-01T00:00:00+00:00'
current_period_end: '2026-11-01T00:00:00+00:00'
trial_ends_at: null
cancel_at: null
canceled_at: null
complimentary: false
complimentary_until: null
metadata: {}
created_at: '2026-10-22T07:20:10+00:00'
updated_at: '2026-10-22T07:20:10+00:00'
properties:
object:
type: string
example: subscription
id:
type: string
example: sub_8xkorpcbz0llchq98drq
project_id:
type: string
example: '84'
subscriber_id:
type: string
example: '44'
plan_id:
type: string
example: '29'
status:
type: string
example: active
current_period_start:
type: string
example: '2026-10-01T00:00:00+00:00'
current_period_end:
type: string
example: '2026-11-01T00:00:00+00:00'
trial_ends_at:
type: string
example: null
nullable: true
cancel_at:
type: string
example: null
nullable: true
canceled_at:
type: string
example: null
nullable: true
complimentary:
type: boolean
example: false
complimentary_until:
type: string
example: null
nullable: true
metadata:
type: object
properties: {}
created_at:
type: string
example: '2026-10-22T07:20:10+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:10+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscriptions:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscriptions:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscriptions:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Subscriptions
parameters:
-
in: path
name: id
description: 'Subscription public id (sub_*) or numeric id.'
example: sub_01JXY...
required: true
schema:
type: string
'/api/v1/subscribers/{external_id}/complimentary':
post:
summary: 'Give a plan for free'
operationId: giveAPlanForFree
description: 'Gives the subscriber a plan with no card and no charge; needs the `subscriptions:complimentary` scope, which `*` keys lack (see "Giving plans for free").'
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
201:
description: 'The complimentary subscription. `complimentary` is `true`; `complimentary_until` is the end date or `null`.'
content:
application/json:
schema:
type: object
example:
object: subscription
id: sub_01HXYZ
project_id: '1'
subscriber_id: '1'
plan_id: '2'
status: active
current_period_start: '2026-10-08T12:00:00+00:00'
current_period_end: null
trial_ends_at: null
cancel_at: null
canceled_at: null
complimentary: true
complimentary_until: '2026-12-31T23:59:59+00:00'
items: []
seats: null
included_seats: null
metadata: []
created_at: '2026-10-08T12:00:00+00:00'
updated_at: '2026-10-08T12:00:00+00:00'
properties:
object:
type: string
example: subscription
id:
type: string
example: sub_01HXYZ
project_id:
type: string
example: '1'
subscriber_id:
type: string
example: '1'
plan_id:
type: string
example: '2'
status:
type: string
example: active
current_period_start:
type: string
example: '2026-10-08T12:00:00+00:00'
current_period_end:
type: string
example: null
nullable: true
trial_ends_at:
type: string
example: null
nullable: true
cancel_at:
type: string
example: null
nullable: true
canceled_at:
type: string
example: null
nullable: true
complimentary:
type: boolean
example: true
complimentary_until:
type: string
example: '2026-12-31T23:59:59+00:00'
items:
type: array
example: []
seats:
type: string
example: null
nullable: true
included_seats:
type: string
example: null
nullable: true
metadata:
type: array
example: []
created_at:
type: string
example: '2026-10-08T12:00:00+00:00'
updated_at:
type: string
example: '2026-10-08T12:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscriptions:complimentary`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscriptions:complimentary'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscriptions:complimentary'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Not Found — the referenced resource does not exist for this project.'
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
-
description: 'Domain error: `subscriber_not_found`.'
type: object
example:
error:
type: subscriber_not_found
message: 'Subscriber not found for this project.'
doc_url: 'https://saasbilling.io/docs#subscriber_not_found'
properties:
error:
type: object
properties:
type:
type: string
example: subscriber_not_found
message:
type: string
example: 'Subscriber not found for this project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#subscriber_not_found'
409:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Domain error: `complimentary_stripe_subscription_active`.'
type: object
example:
error:
type: complimentary_stripe_subscription_active
message: 'This subscriber already pays for a plan. Cancel that paid plan first, then give a plan for free.'
doc_url: 'https://saasbilling.io/docs#complimentary_stripe_subscription_active'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_stripe_subscription_active
message:
type: string
example: 'This subscriber already pays for a plan. Cancel that paid plan first, then give a plan for free.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_stripe_subscription_active'
-
description: 'Domain error: `complimentary_already_active`.'
type: object
example:
error:
type: complimentary_already_active
message: 'This subscriber already has a plan for free. Change or end it instead.'
doc_url: 'https://saasbilling.io/docs#complimentary_already_active'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_already_active
message:
type: string
example: 'This subscriber already has a plan for free. Change or end it instead.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_already_active'
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Domain error: `complimentary_plan_unavailable`.'
type: object
example:
error:
type: complimentary_plan_unavailable
message: 'This plan cannot be given right now. Pick an active plan of this project.'
doc_url: 'https://saasbilling.io/docs#complimentary_plan_unavailable'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_plan_unavailable
message:
type: string
example: 'This plan cannot be given right now. Pick an active plan of this project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_plan_unavailable'
-
description: 'Domain error: `complimentary_until_in_past`.'
type: object
example:
error:
type: complimentary_until_in_past
message: 'The end date has already passed. Pick a later date or leave it empty.'
doc_url: 'https://saasbilling.io/docs#complimentary_until_in_past'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_until_in_past
message:
type: string
example: 'The end date has already passed. Pick a later date or leave it empty.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_until_in_past'
-
description: 'Domain error: `complimentary_reason_required`.'
type: object
example:
error:
type: complimentary_reason_required
message: 'Write a short reason so your team knows why this plan is free.'
doc_url: 'https://saasbilling.io/docs#complimentary_reason_required'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_reason_required
message:
type: string
example: 'Write a short reason so your team knows why this plan is free.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_reason_required'
tags:
- Subscriptions
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
plan:
type: string
description: 'Key of the plan to give (any active plan of your project except the default plan, hidden plans included).'
example: pro
ends_at:
type: string
description: 'When the plan stops being free: an ISO-8601 timestamp, or a date (`YYYY-MM-DD`) meaning the end of that day in UTC. Omit it or send null for no end date.'
example: '2026-12-31'
reason:
type: string
description: "Why the plan is free (max 1000 characters). Kept in your project's audit log; never sent to the subscriber or in webhooks."
example: 'Partner school'
required:
- plan
- reason
patch:
summary: 'Change a free plan'
operationId: changeAFreePlan
description: 'Changes the plan, end date or reason of the free plan; send only what changes, and `ends_at: null` to remove the end date.'
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: 'The updated complimentary subscription.'
content:
application/json:
schema:
type: object
example:
object: subscription
id: sub_01HXYZ
project_id: '1'
subscriber_id: '1'
plan_id: '2'
status: active
current_period_start: '2026-10-08T12:00:00+00:00'
current_period_end: null
trial_ends_at: null
cancel_at: null
canceled_at: null
complimentary: true
complimentary_until: '2026-12-31T23:59:59+00:00'
items: []
seats: null
included_seats: null
metadata: []
created_at: '2026-10-08T12:00:00+00:00'
updated_at: '2026-10-08T12:00:00+00:00'
properties:
object:
type: string
example: subscription
id:
type: string
example: sub_01HXYZ
project_id:
type: string
example: '1'
subscriber_id:
type: string
example: '1'
plan_id:
type: string
example: '2'
status:
type: string
example: active
current_period_start:
type: string
example: '2026-10-08T12:00:00+00:00'
current_period_end:
type: string
example: null
nullable: true
trial_ends_at:
type: string
example: null
nullable: true
cancel_at:
type: string
example: null
nullable: true
canceled_at:
type: string
example: null
nullable: true
complimentary:
type: boolean
example: true
complimentary_until:
type: string
example: '2026-12-31T23:59:59+00:00'
items:
type: array
example: []
seats:
type: string
example: null
nullable: true
included_seats:
type: string
example: null
nullable: true
metadata:
type: array
example: []
created_at:
type: string
example: '2026-10-08T12:00:00+00:00'
updated_at:
type: string
example: '2026-10-08T12:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `subscriptions:complimentary`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscriptions:complimentary'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscriptions:complimentary'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Not Found — the referenced resource does not exist for this project.'
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
-
description: 'Domain error: `subscriber_not_found`.'
type: object
example:
error:
type: subscriber_not_found
message: 'Subscriber not found for this project.'
doc_url: 'https://saasbilling.io/docs#subscriber_not_found'
properties:
error:
type: object
properties:
type:
type: string
example: subscriber_not_found
message:
type: string
example: 'Subscriber not found for this project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#subscriber_not_found'
-
description: 'Domain error: `complimentary_not_found`.'
type: object
example:
error:
type: complimentary_not_found
message: 'This subscriber has no free plan to change or end.'
doc_url: 'https://saasbilling.io/docs#complimentary_not_found'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_not_found
message:
type: string
example: 'This subscriber has no free plan to change or end.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_not_found'
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Domain error: `complimentary_plan_unavailable`.'
type: object
example:
error:
type: complimentary_plan_unavailable
message: 'This plan cannot be given right now. Pick an active plan of this project.'
doc_url: 'https://saasbilling.io/docs#complimentary_plan_unavailable'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_plan_unavailable
message:
type: string
example: 'This plan cannot be given right now. Pick an active plan of this project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_plan_unavailable'
-
description: 'Domain error: `complimentary_until_in_past`.'
type: object
example:
error:
type: complimentary_until_in_past
message: 'The end date has already passed. Pick a later date or leave it empty.'
doc_url: 'https://saasbilling.io/docs#complimentary_until_in_past'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_until_in_past
message:
type: string
example: 'The end date has already passed. Pick a later date or leave it empty.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_until_in_past'
-
description: 'Domain error: `complimentary_reason_required`.'
type: object
example:
error:
type: complimentary_reason_required
message: 'Write a short reason so your team knows why this plan is free.'
doc_url: 'https://saasbilling.io/docs#complimentary_reason_required'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_reason_required
message:
type: string
example: 'Write a short reason so your team knows why this plan is free.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_reason_required'
tags:
- Subscriptions
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
plan:
type: string
description: 'Key of the plan to give instead.'
example: team
ends_at:
type: string
description: 'New end date (ISO-8601 timestamp, or `YYYY-MM-DD` for the end of that day in UTC), or null for no end date.'
example: '2027-01-31'
reason:
type: string
description: 'New reason (max 1000 characters), kept in your audit log.'
example: 'Moved to the team tier'
delete:
summary: 'End a free plan'
operationId: endAFreePlan
description: 'Ends the free plan now and puts the subscriber back on your project''s default plan, as the panel''s "End free plan" does.'
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
204:
description: 'Free plan ended; the subscriber is back on the default plan.'
content:
application/json:
schema:
type: object
nullable: true
403:
description: 'Forbidden — API key lacks the required scope `subscriptions:complimentary`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: subscriptions:complimentary'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: subscriptions:complimentary'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Not Found — the referenced resource does not exist for this project.'
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
-
description: 'Domain error: `subscriber_not_found`.'
type: object
example:
error:
type: subscriber_not_found
message: 'Subscriber not found for this project.'
doc_url: 'https://saasbilling.io/docs#subscriber_not_found'
properties:
error:
type: object
properties:
type:
type: string
example: subscriber_not_found
message:
type: string
example: 'Subscriber not found for this project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#subscriber_not_found'
-
description: 'Domain error: `complimentary_not_found`.'
type: object
example:
error:
type: complimentary_not_found
message: 'This subscriber has no free plan to change or end.'
doc_url: 'https://saasbilling.io/docs#complimentary_not_found'
properties:
error:
type: object
properties:
type:
type: string
example: complimentary_not_found
message:
type: string
example: 'This subscriber has no free plan to change or end.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#complimentary_not_found'
tags:
- Subscriptions
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
reason:
type: string
description: 'Why the free plan ends (max 1000 characters), kept in your audit log.'
example: 'Partnership ended'
required:
- reason
parameters:
-
in: path
name: external_id
description: 'The external ID of the subscriber.'
example: user_12345
required: true
schema:
type: string
/api/v1/plans:
get:
summary: 'List plans'
operationId: listPlans
description: "Returns the active plans in your project, each with its active prices\nand the currency they are billed in. Use this to render a pricing page in\nyour own UI; subscribers pick and pay for a plan inside the SaasBilling\nbilling portal (`POST /api/v1/portal-sessions`).\n\nPlans the operator marked hidden are not listed here. A hidden plan is\nstill returned by `GET /api/v1/plans/{key}` and still applies to the\nsubscribers assigned to it."
parameters:
-
in: query
name: currency
description: 'Filter plans by currency code (e.g. USD, EUR).'
example: USD
required: false
schema:
type: string
description: 'Filter plans by currency code (e.g. USD, EUR).'
example: USD
responses:
200:
description: "List of active plans, hidden plans left out. `data[]` items follow the Plan resource shape, each with its catalog `features[]` and active `prices[]`. Use `features[]` to render what a plan includes without creating a subscriber, and `is_default` to resolve the project's default/free tier — never infer it from `key`."
content:
application/json:
schema:
type: object
example:
object: list
data:
-
object: plan
id: '1'
key: pro_monthly
locale: en
name: 'Pro Monthly'
description: 'Professional plan billed monthly.'
status: active
is_default: false
metadata:
highlight: 'true'
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-01T00:00:00+00:00'
features:
-
key: projects
name: Projects
type: quota
value: 10
-
key: sso
name: SSO
type: boolean
value: true
-
key: ai_tokens
name: 'AI tokens'
type: quota
value: 100000
prices: []
properties:
object:
type: string
example: list
data:
type: array
example:
-
object: plan
id: '1'
key: pro_monthly
locale: en
name: 'Pro Monthly'
description: 'Professional plan billed monthly.'
status: active
is_default: false
metadata:
highlight: 'true'
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-01T00:00:00+00:00'
features:
- key: projects
name: Projects
type: quota
value: 10
- key: sso
name: SSO
type: boolean
value: true
- key: ai_tokens
name: 'AI tokens'
type: quota
value: 100000
prices: []
items:
type: object
properties:
object:
type: string
example: plan
id:
type: string
example: '1'
key:
type: string
example: pro_monthly
locale:
type: string
example: en
name:
type: string
example: 'Pro Monthly'
description:
type: string
example: 'Professional plan billed monthly.'
status:
type: string
example: active
is_default:
type: boolean
example: false
metadata:
type: object
properties:
highlight:
type: string
example: 'true'
created_at:
type: string
example: '2026-05-01T00:00:00+00:00'
updated_at:
type: string
example: '2026-05-01T00:00:00+00:00'
features:
type: array
example:
- key: projects
name: Projects
type: quota
value: 10
- key: sso
name: SSO
type: boolean
value: true
- key: ai_tokens
name: 'AI tokens'
type: quota
value: 100000
items:
type: object
properties:
key:
type: string
example: projects
name:
type: string
example: Projects
type:
type: string
example: quota
value:
type: integer
example: 10
prices:
type: array
example: []
403:
description: 'Forbidden — API key lacks the required scope `plans:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: plans:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: plans:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
tags:
- Plans
'/api/v1/plans/{key}':
get:
summary: 'Retrieve a plan by key'
operationId: retrieveAPlanByKey
description: "Returns a single plan and its active prices, identified by the\nhuman-readable plan key (e.g. `pro`, `basic-monthly`). Prefer this over\nnumeric ids when wiring up your pricing UI."
parameters: []
responses:
200:
description: 'A single plan with its catalog `features[]` and active `prices[]`.'
content:
application/json:
schema:
type: object
example:
object: plan
id: '1'
key: pro_monthly
locale: en
name: 'Pro Monthly'
description: 'Professional plan billed monthly.'
status: active
is_default: false
metadata:
highlight: 'true'
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-01T00:00:00+00:00'
features:
-
key: projects
name: Projects
type: quota
value: 10
-
key: sso
name: SSO
type: boolean
value: true
prices: []
properties:
object:
type: string
example: plan
id:
type: string
example: '1'
key:
type: string
example: pro_monthly
locale:
type: string
example: en
name:
type: string
example: 'Pro Monthly'
description:
type: string
example: 'Professional plan billed monthly.'
status:
type: string
example: active
is_default:
type: boolean
example: false
metadata:
type: object
properties:
highlight:
type: string
example: 'true'
created_at:
type: string
example: '2026-05-01T00:00:00+00:00'
updated_at:
type: string
example: '2026-05-01T00:00:00+00:00'
features:
type: array
example:
- key: projects
name: Projects
type: quota
value: 10
- key: sso
name: SSO
type: boolean
value: true
items:
type: object
properties:
key:
type: string
example: projects
name:
type: string
example: Projects
type:
type: string
example: quota
value:
type: integer
example: 10
prices:
type: array
example: []
403:
description: 'Forbidden — API key lacks the required scope `plans:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: plans:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: plans:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Plans
parameters:
-
in: path
name: key
description: 'The key of the plan.'
example: basic-monthly
required: true
schema:
type: string
/api/v1/portal-sessions:
post:
summary: 'Create a billing portal session'
operationId: createABillingPortalSession
description: "Issues a single-use magic link to the project's whitelabel SaasBilling\nbilling portal so the subscriber can choose or change a plan, update their\npayment method, view invoices and cancel on their own. Redirect them to\nthe returned `url`; when they leave the portal they are sent back to\n`return_url`.\n\nTreat `url` as opaque: its host is your verified custom domain when one is\nconfigured, otherwise the SaasBilling shared host. Do not parse or rebuild it."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: 'Portal session created. Redirect the subscriber to the returned URL.'
content:
application/json:
schema:
type: object
example:
url: 'https://billing.example.com/portal/abcdef0123456789'
expires_at: '2026-01-15T10:15:00+00:00'
properties:
url:
type: string
example: 'https://billing.example.com/portal/abcdef0123456789'
expires_at:
type: string
example: '2026-01-15T10:15:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `portal:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: portal:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: portal:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Domain error: `subscriber_not_found`.'
content:
application/json:
schema:
type: object
example:
error:
type: subscriber_not_found
message: 'Subscriber not found for this project.'
doc_url: 'https://saasbilling.io/docs#subscriber_not_found'
properties:
error:
type: object
properties:
type:
type: string
example: subscriber_not_found
message:
type: string
example: 'Subscriber not found for this project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#subscriber_not_found'
409:
description: 'Domain error: `stripe_not_connected`.'
content:
application/json:
schema:
type: object
example:
error:
type: stripe_not_connected
message: 'Connect and verify your Stripe account before using checkout or billing portal.'
doc_url: 'https://saasbilling.io/docs#stripe_not_connected'
properties:
error:
type: object
properties:
type:
type: string
example: stripe_not_connected
message:
type: string
example: 'Connect and verify your Stripe account before using checkout or billing portal.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#stripe_not_connected'
422:
description: 'Domain error: `subscriber_wrong_project`.'
content:
application/json:
schema:
type: object
example:
error:
type: subscriber_wrong_project
message: 'Subscriber does not belong to the authenticated project.'
doc_url: 'https://saasbilling.io/docs#subscriber_wrong_project'
properties:
error:
type: object
properties:
type:
type: string
example: subscriber_wrong_project
message:
type: string
example: 'Subscriber does not belong to the authenticated project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#subscriber_wrong_project'
tags:
- Portal
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
subscriber_external_id:
type: string
description: 'External identifier of the subscriber to authenticate into the portal.'
example: user_12345
return_url:
type: string
description: 'URL the portal redirects the subscriber to when they leave.'
example: 'https://app.example.com/account'
locale:
type: string
description: "Language this portal visit is rendered in (one of the project's attached locales). Overrides the subscriber's own locale for this session only and is not stored on the subscriber. Omit it to use the subscriber's locale, or the project default when they have none."
example: fr
required:
- subscriber_external_id
- return_url
'/api/v1/subscribers/{external_id}/entitlements':
get:
summary: "Resolve a subscriber's entitlements"
operationId: resolveASubscribersEntitlements
description: "Returns the resolved feature flags and limits the subscriber currently\nhas access to, based on their active subscription's plan. Cache this on\nthe client for 60 seconds — SaasBilling returns a strong `ETag` so you can\nrevalidate with `If-None-Match` for a cheap `304 Not Modified`.\n\nAn entry states what the plan *grants*, never what is consumed: SaasBilling\ndoes not know how many devices or projects you have created. Count usage in\nyour own application and compare it against `value` (`-1` means unlimited).\n\n`seats` is different, and is the one number here that is billed rather than\ngranted: it is the count of users the subscription currently pays for, and it\nis the **real** user count, not a count above an included allowance. A plan\nsold as \"first 3 users included, then 5.00 each\" reports `seats: 5` for a\nfive-user organisation, and the first three are billed at zero by the price\nitself. Enforce it directly; never apply an offset of your own. There is no\nendpoint for changing it — the subscriber does that in the hosted billing\nportal, and you learn about it here and from `subscription.updated`.\n\n`plan.included_seats` is the other half of that number: how many users the\nseat price bills at zero before it starts charging, or `null` when it\ncharges from the first user. Together with `seats` it is what lets you show\n\"5 of 3 included\" without a second call to the catalog endpoint. It\ndescribes *billing*, not permission — the limit you enforce is the plan's\nown quota entry (commonly `member`) in `entries`.\n\n`subscription` is `null` for a subscriber with no live subscription, and\n`plan.is_default` marks the project's free tier — never infer the free\nplan from `plan.key`, which each project chooses for itself."
parameters:
-
in: header
name: If-None-Match
description: ''
example: '"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"'
schema:
type: string
responses:
200:
description: 'Returned with `Cache-Control: private, max-age=60` and a strong `ETag` of the entitlement content (`generated_at` excluded, so a recomputed but unchanged set keeps its ETag). Send that ETag back as `If-None-Match` for a cheap revalidation.'
content:
application/json:
schema:
type: object
example:
object: entitlements
data:
subscriber_id: 42
project_id: 7
subscription_id: 311
plan:
key: pro
name: Pro
description: 'Professional plan.'
is_default: false
interval_unit: month
interval_count: 1
included_seats: 3
seats: 5
subscription:
status: active
cancel_at: null
current_period_start: '2026-06-01T00:00:00+00:00'
current_period_end: '2026-07-01T00:00:00+00:00'
trial_ends_at: null
entries:
-
key: projects
type: quota
value: 5
labels: []
period_start: '2026-06-01T00:00:00+00:00'
period_end: '2026-07-01T00:00:00+00:00'
meter_key: project_events
-
key: sso
type: boolean
value: true
labels: []
period_start: '2026-06-01T00:00:00+00:00'
period_end: '2026-07-01T00:00:00+00:00'
meter_key: null
-
key: support
type: text
value: knowledge_base
labels:
en: 'Knowledge base'
tr: 'Bilgi bankası'
period_start: '2026-06-01T00:00:00+00:00'
period_end: '2026-07-01T00:00:00+00:00'
meter_key: null
generated_at: '2026-06-10T12:00:00+00:00'
properties:
object:
type: string
example: entitlements
data:
type: object
properties:
subscriber_id:
type: integer
example: 42
project_id:
type: integer
example: 7
subscription_id:
type: integer
example: 311
plan:
type: object
properties:
key:
type: string
example: pro
name:
type: string
example: Pro
description:
type: string
example: 'Professional plan.'
is_default:
type: boolean
example: false
interval_unit:
type: string
example: month
interval_count:
type: integer
example: 1
included_seats:
type: integer
example: 3
seats:
type: integer
example: 5
subscription:
type: object
properties:
status:
type: string
example: active
cancel_at:
type: string
example: null
nullable: true
current_period_start:
type: string
example: '2026-06-01T00:00:00+00:00'
current_period_end:
type: string
example: '2026-07-01T00:00:00+00:00'
trial_ends_at:
type: string
example: null
nullable: true
entries:
type: array
example:
-
key: projects
type: quota
value: 5
labels: []
period_start: '2026-06-01T00:00:00+00:00'
period_end: '2026-07-01T00:00:00+00:00'
meter_key: project_events
-
key: sso
type: boolean
value: true
labels: []
period_start: '2026-06-01T00:00:00+00:00'
period_end: '2026-07-01T00:00:00+00:00'
meter_key: null
-
key: support
type: text
value: knowledge_base
labels:
en: 'Knowledge base'
tr: 'Bilgi bankası'
period_start: '2026-06-01T00:00:00+00:00'
period_end: '2026-07-01T00:00:00+00:00'
meter_key: null
items:
type: object
properties:
key:
type: string
example: projects
type:
type: string
example: quota
value:
type: integer
example: 5
labels:
type: array
example: []
period_start:
type: string
example: '2026-06-01T00:00:00+00:00'
period_end:
type: string
example: '2026-07-01T00:00:00+00:00'
meter_key:
type: string
example: project_events
generated_at:
type: string
example: '2026-06-10T12:00:00+00:00'
304:
description: 'Entitlement set unchanged since the ETag in `If-None-Match`.'
content:
application/json:
schema:
type: object
nullable: true
403:
description: 'Forbidden — API key lacks the required scope `entitlements:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: entitlements:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: entitlements:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Entitlements
parameters:
-
in: path
name: external_id
description: 'External identifier of the subscriber.'
example: user_12345
required: true
schema:
type: string
'/api/v1/subscribers/{external_id}/usage':
get:
summary: "Get a subscriber's usage summary"
operationId: getASubscribersUsageSummary
description: "Aggregates the subscriber's recorded usage for a single meter within the\ncurrent billing period. Returns `404 no_active_subscription` when the\nsubscriber has no active subscription to report against."
parameters:
-
in: query
name: meter_key
description: 'Meter key to summarise usage for.'
example: api_calls
required: true
schema:
type: string
description: 'Meter key to summarise usage for.'
example: api_calls
-
in: query
name: period
description: 'Period to aggregate: "current" (default — the active billing period), "last" (the billing period immediately before the current one), or "last_30d" (rolling window over the last 30 days).'
example: current
required: false
schema:
type: string
description: 'Period to aggregate: "current" (default — the active billing period), "last" (the billing period immediately before the current one), or "last_30d" (rolling window over the last 30 days).'
example: current
responses:
200:
description: "Aggregated usage for the subscriber's active subscription, scoped to the current billing period."
content:
application/json:
schema:
type: object
example:
object: usage_summary
meter_key: api_calls
quantity: 1284
period_start: '2026-05-01T00:00:00+00:00'
period_end: '2026-06-01T00:00:00+00:00'
subscription_public_id: sub_01JXY...
properties:
object:
type: string
example: usage_summary
meter_key:
type: string
example: api_calls
quantity:
type: integer
example: 1284
period_start:
type: string
example: '2026-05-01T00:00:00+00:00'
period_end:
type: string
example: '2026-06-01T00:00:00+00:00'
subscription_public_id:
type: string
example: sub_01JXY...
403:
description: 'Forbidden — API key lacks the required scope `usage:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: usage:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: usage:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Subscriber has no active subscription to summarise usage against.'
type: object
example:
error:
type: no_active_subscription
message: 'Subscriber has no active subscription.'
doc_url: '/docs#no_active_subscription'
properties:
error:
type: object
properties:
type:
type: string
example: no_active_subscription
message:
type: string
example: 'Subscriber has no active subscription.'
doc_url:
type: string
example: '/docs#no_active_subscription'
-
description: 'Not Found — the referenced resource does not exist for this project.'
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Usage
parameters:
-
in: path
name: external_id
description: 'External identifier of the subscriber.'
example: user_12345
required: true
schema:
type: string
/api/v1/usage:
post:
summary: 'Record metered usage'
operationId: recordMeteredUsage
description: "Records a usage event against the subscriber's active metered\nsubscription item. Use this whenever the subscriber consumes a billable\nfeature (API call, transcoded video, sent email, …).\n\n**This call is what creates the charge.** A metered line item does not\nexist until usage is recorded, so a call you skip is revenue the merchant\nnever bills, and a call you make twice is a customer overcharged — which is\nwhy `idempotency_key` is required rather than optional. Pass a stable key\nper logical event; the same key always returns the same record, so it is\nsafe to retry on network errors.\n\nThe recorded quantity is forwarded to Stripe and reflected in the next\ninvoice for the subscriber's current billing period. A metered amount is\ntherefore **not knowable in advance** — it can only be priced once the\nperiod closes. Do not build a UI that promises a total for the period in\nprogress; show consumption so far and say that usage is billed on top of\nthe recurring amount.\n\nA metered Meter and a `quota` feature are different things and often\nappear on the same plan: the quota is what the plan *grants* (read it from\nthe entitlements endpoint), the meter is what gets *billed* (this call)."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: usage_record
id: '6'
meter_id: '8'
quantity: 81
recorded_at: '2026-10-22T07:20:11+00:00'
idempotency_key: usage_znltcxz0otfyjnewju95jokn
created_at: '2026-10-22T07:20:11+00:00'
updated_at: '2026-10-22T07:20:11+00:00'
properties:
object:
type: string
example: usage_record
id:
type: string
example: '6'
meter_id:
type: string
example: '8'
quantity:
type: integer
example: 81
recorded_at:
type: string
example: '2026-10-22T07:20:11+00:00'
idempotency_key:
type: string
example: usage_znltcxz0otfyjnewju95jokn
created_at:
type: string
example: '2026-10-22T07:20:11+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:11+00:00'
403:
description: 'Forbidden — API key lacks the required scope `usage:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: usage:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: usage:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Domain error: `no_active_subscription`.'
content:
application/json:
schema:
type: object
example:
error:
type: no_active_subscription
message: 'This subscriber has no active subscription.'
doc_url: 'https://saasbilling.io/docs#no_active_subscription'
properties:
error:
type: object
properties:
type:
type: string
example: no_active_subscription
message:
type: string
example: 'This subscriber has no active subscription.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#no_active_subscription'
422:
description: ''
content:
application/json:
schema:
oneOf:
-
description: 'Domain error: `usage_invalid_quantity`.'
type: object
example:
error:
type: usage_invalid_quantity
message: 'Usage quantity must be a positive integer.'
doc_url: 'https://saasbilling.io/docs#usage_invalid_quantity'
properties:
error:
type: object
properties:
type:
type: string
example: usage_invalid_quantity
message:
type: string
example: 'Usage quantity must be a positive integer.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#usage_invalid_quantity'
-
description: 'Domain error: `usage_correction_requires_sum_aggregation`.'
type: object
example:
error:
type: usage_correction_requires_sum_aggregation
message: 'Usage corrections require sum aggregation.'
doc_url: 'https://saasbilling.io/docs#usage_correction_requires_sum_aggregation'
properties:
error:
type: object
properties:
type:
type: string
example: usage_correction_requires_sum_aggregation
message:
type: string
example: 'Usage corrections require sum aggregation.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#usage_correction_requires_sum_aggregation'
-
description: 'Domain error: `usage_recorded_at_in_future`.'
type: object
example:
error:
type: usage_recorded_at_in_future
message: 'recorded_at must be at or before the current server time.'
doc_url: 'https://saasbilling.io/docs#usage_recorded_at_in_future'
properties:
error:
type: object
properties:
type:
type: string
example: usage_recorded_at_in_future
message:
type: string
example: 'recorded_at must be at or before the current server time.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#usage_recorded_at_in_future'
-
description: 'Domain error: `usage_recorded_at_too_old`.'
type: object
example:
error:
type: usage_recorded_at_too_old
message: 'recorded_at is older than the accepted backdating window.'
doc_url: 'https://saasbilling.io/docs#usage_recorded_at_too_old'
properties:
error:
type: object
properties:
type:
type: string
example: usage_recorded_at_too_old
message:
type: string
example: 'recorded_at is older than the accepted backdating window.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#usage_recorded_at_too_old'
-
description: 'Domain error: `usage_subscription_not_active`.'
type: object
example:
error:
type: usage_subscription_not_active
message: 'Subscription is not active; usage cannot be recorded.'
doc_url: 'https://saasbilling.io/docs#usage_subscription_not_active'
properties:
error:
type: object
properties:
type:
type: string
example: usage_subscription_not_active
message:
type: string
example: 'Subscription is not active; usage cannot be recorded.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#usage_subscription_not_active'
-
description: 'Domain error: `usage_no_metered_item_for_meter`.'
type: object
example:
error:
type: usage_no_metered_item_for_meter
message: 'No metered subscription item is bound to this meter.'
doc_url: 'https://saasbilling.io/docs#usage_no_metered_item_for_meter'
properties:
error:
type: object
properties:
type:
type: string
example: usage_no_metered_item_for_meter
message:
type: string
example: 'No metered subscription item is bound to this meter.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#usage_no_metered_item_for_meter'
-
description: 'Domain error: `plan_cross_project_resource`.'
type: object
example:
error:
type: plan_cross_project_resource
message: 'Plan resource belongs to a different project.'
doc_url: 'https://saasbilling.io/docs#plan_cross_project_resource'
properties:
error:
type: object
properties:
type:
type: string
example: plan_cross_project_resource
message:
type: string
example: 'Plan resource belongs to a different project.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#plan_cross_project_resource'
tags:
- Usage
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
subscriber_external_id:
type: string
description: 'External identifier of the subscriber whose subscription will be charged.'
example: user_12345
meter_key:
type: string
description: 'Meter key to record usage against.'
example: api_calls
quantity:
type: integer
description: 'Quantity to record. Non-zero integer. Positive values record consumption; negative values post a correction (only allowed on meters using `sum` aggregation).'
example: 10
idempotency_key:
type: string
description: 'Unique key to deduplicate this usage record (max 100 chars — forwarded to Stripe as the meter event identifier). Same key returns the same record.'
example: 01JXY7QGJ8KZ...
recorded_at:
type: string
description: 'ISO-8601 timestamp when usage was observed. Defaults to the server time.'
example: '2026-01-15T10:00:00Z'
required:
- subscriber_external_id
- meter_key
- quantity
- idempotency_key
'/api/v1/subscribers/{external_id}/invoices':
get:
summary: "List a subscriber's invoices"
operationId: listASubscribersInvoices
description: "Returns invoices for the subscriber in reverse chronological order,\nincluding hosted invoice URLs and PDF links generated by Stripe. Ideal\nfor a \"Billing history\" tab in your application."
parameters: []
responses:
200:
description: 'List of invoices for the subscriber, newest first. `data[]` items follow the Invoice resource shape.'
content:
application/json:
schema:
type: object
example:
object: list
data:
-
object: invoice
id: inv_01HXYZ
subscription_id: '1'
currency_id: '1'
currency_code: USD
status: paid
amount_due: 2900
amount_paid: 2900
hosted_invoice_url: 'https://invoice.stripe.com/i/acct_xxx/inv_xxx'
pdf_url: 'https://pay.stripe.com/invoice/xxx/pdf'
period_start: '2026-05-01T00:00:00+00:00'
period_end: '2026-06-01T00:00:00+00:00'
paid_at: '2026-05-02T10:00:00+00:00'
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-02T10:00:00+00:00'
properties:
object:
type: string
example: list
data:
type: array
example:
- object: invoice
id: inv_01HXYZ
subscription_id: '1'
currency_id: '1'
currency_code: USD
status: paid
amount_due: 2900
amount_paid: 2900
hosted_invoice_url: 'https://invoice.stripe.com/i/acct_xxx/inv_xxx'
pdf_url: 'https://pay.stripe.com/invoice/xxx/pdf'
period_start: '2026-05-01T00:00:00+00:00'
period_end: '2026-06-01T00:00:00+00:00'
paid_at: '2026-05-02T10:00:00+00:00'
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-02T10:00:00+00:00'
items:
type: object
properties:
object:
type: string
example: invoice
id:
type: string
example: inv_01HXYZ
subscription_id:
type: string
example: '1'
currency_id:
type: string
example: '1'
currency_code:
type: string
example: USD
status:
type: string
example: paid
amount_due:
type: integer
example: 2900
amount_paid:
type: integer
example: 2900
hosted_invoice_url:
type: string
example: 'https://invoice.stripe.com/i/acct_xxx/inv_xxx'
pdf_url:
type: string
example: 'https://pay.stripe.com/invoice/xxx/pdf'
period_start:
type: string
example: '2026-05-01T00:00:00+00:00'
period_end:
type: string
example: '2026-06-01T00:00:00+00:00'
paid_at:
type: string
example: '2026-05-02T10:00:00+00:00'
created_at:
type: string
example: '2026-05-01T00:00:00+00:00'
updated_at:
type: string
example: '2026-05-02T10:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `invoices:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: invoices:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: invoices:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Invoices
parameters:
-
in: path
name: external_id
description: 'External identifier of the subscriber.'
example: user_12345
required: true
schema:
type: string
'/api/v1/invoices/{id}':
get:
summary: 'Retrieve an invoice'
operationId: retrieveAnInvoice
description: "Returns a single invoice by its public id (`in_*`) or numeric id,\nincluding the hosted Stripe URL and PDF download link."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: invoice
id: in_ocmaxxf8iaspsvjshvtf
subscription_id: '34'
currency_id: '1'
status: open
amount_due: 49055
amount_paid: 0
hosted_invoice_url: null
pdf_url: null
period_start: '2026-10-01T00:00:00+00:00'
period_end: '2026-11-01T00:00:00+00:00'
paid_at: null
created_at: '2026-10-22T07:20:10+00:00'
updated_at: '2026-10-22T07:20:10+00:00'
properties:
object:
type: string
example: invoice
id:
type: string
example: in_ocmaxxf8iaspsvjshvtf
subscription_id:
type: string
example: '34'
currency_id:
type: string
example: '1'
status:
type: string
example: open
amount_due:
type: integer
example: 49055
amount_paid:
type: integer
example: 0
hosted_invoice_url:
type: string
example: null
nullable: true
pdf_url:
type: string
example: null
nullable: true
period_start:
type: string
example: '2026-10-01T00:00:00+00:00'
period_end:
type: string
example: '2026-11-01T00:00:00+00:00'
paid_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-10-22T07:20:10+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:10+00:00'
403:
description: 'Forbidden — API key lacks the required scope `invoices:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: invoices:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: invoices:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- Invoices
parameters:
-
in: path
name: id
description: 'Invoice public id (in_*) or numeric id.'
example: in_01JXY...
required: true
schema:
type: string
/api/v1/webhook-events:
get:
summary: 'List subscribable webhook event types'
operationId: listSubscribableWebhookEventTypes
description: "Machine-readable catalog of every event type an endpoint can subscribe\nto. Use it to render a subscription picker or to validate your stored\nevent list against the current platform catalog instead of hardcoding\nevent names."
parameters: []
responses:
200:
description: 'Every subscribable event type with its human-readable label.'
content:
application/json:
schema:
type: object
example:
object: list
data:
-
type: subscriber.created
label: 'Subscriber created'
-
type: subscription.created
label: 'Subscription created'
-
type: entitlement.updated
label: 'Entitlements updated'
properties:
object:
type: string
example: list
data:
type: array
example:
- type: subscriber.created
label: 'Subscriber created'
- type: subscription.created
label: 'Subscription created'
- type: entitlement.updated
label: 'Entitlements updated'
items:
type: object
properties:
type:
type: string
example: subscriber.created
label:
type: string
example: 'Subscriber created'
403:
description: 'Forbidden — API key lacks the required scope `webhooks:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
tags:
- 'Webhook Endpoints'
/api/v1/webhook-endpoints:
get:
summary: 'List webhook endpoints'
operationId: listWebhookEndpoints
description: "Returns every webhook endpoint configured for your project, with their\ncurrent status and which event types they subscribe to. Use this to\nrender a webhooks settings page in your dashboard."
parameters: []
responses:
200:
description: 'List of webhook endpoints. `data[]` items follow the WebhookEndpoint resource shape.'
content:
application/json:
schema:
type: object
example:
object: list
data:
-
object: webhook_endpoint
id: 1
url: 'https://example.test/webhooks/saasbilling'
description: 'Production webhook'
status: active
event_types:
- subscription.created
- invoice.paid
consecutive_failures: 0
last_success_at: '2026-05-20T09:00:00+00:00'
last_failure_at: null
signing_secret_grace_ends_at: null
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-20T09:00:00+00:00'
properties:
object:
type: string
example: list
data:
type: array
example:
-
object: webhook_endpoint
id: 1
url: 'https://example.test/webhooks/saasbilling'
description: 'Production webhook'
status: active
event_types:
- subscription.created
- invoice.paid
consecutive_failures: 0
last_success_at: '2026-05-20T09:00:00+00:00'
last_failure_at: null
signing_secret_grace_ends_at: null
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-20T09:00:00+00:00'
items:
type: object
properties:
object:
type: string
example: webhook_endpoint
id:
type: integer
example: 1
url:
type: string
example: 'https://example.test/webhooks/saasbilling'
description:
type: string
example: 'Production webhook'
status:
type: string
example: active
event_types:
type: array
example:
- subscription.created
- invoice.paid
items:
type: string
consecutive_failures:
type: integer
example: 0
last_success_at:
type: string
example: '2026-05-20T09:00:00+00:00'
last_failure_at:
type: string
example: null
nullable: true
signing_secret_grace_ends_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-05-01T00:00:00+00:00'
updated_at:
type: string
example: '2026-05-20T09:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `webhooks:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
tags:
- 'Webhook Endpoints'
post:
summary: 'Create a webhook endpoint'
operationId: createAWebhookEndpoint
description: "Registers a new HTTPS URL to receive SaasBilling webhook deliveries. The\nresponse contains a one-time-visible signing `secret` — store it\nsecurely on your server and use it to verify the `X-SaasBilling-Signature`\nheader on every delivery."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
201:
description: 'Webhook endpoint created. `signing_secret` is exposed **once** — store it securely; subsequent reads of this endpoint will omit it.'
content:
application/json:
schema:
type: object
example:
object: webhook_endpoint
id: 1
url: 'https://app.example.com/webhooks/saasbilling'
description: 'Production receiver'
status: active
event_types:
- subscription.created
- invoice.paid
consecutive_failures: 0
last_success_at: null
last_failure_at: null
signing_secret: whsec_01JXY7Z3K2M5N6P8Q9R0S1T2U3
signing_secret_grace_ends_at: null
created_at: '2026-05-20T10:00:00+00:00'
updated_at: '2026-05-20T10:00:00+00:00'
properties:
object:
type: string
example: webhook_endpoint
id:
type: integer
example: 1
url:
type: string
example: 'https://app.example.com/webhooks/saasbilling'
description:
type: string
example: 'Production receiver'
status:
type: string
example: active
event_types:
type: array
example:
- subscription.created
- invoice.paid
items:
type: string
consecutive_failures:
type: integer
example: 0
last_success_at:
type: string
example: null
nullable: true
last_failure_at:
type: string
example: null
nullable: true
signing_secret:
type: string
example: whsec_01JXY7Z3K2M5N6P8Q9R0S1T2U3
signing_secret_grace_ends_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-05-20T10:00:00+00:00'
updated_at:
type: string
example: '2026-05-20T10:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `webhooks:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
tags:
- 'Webhook Endpoints'
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
url:
type: string
description: 'HTTPS URL to receive webhook deliveries.'
example: 'https://app.example.com/webhooks/saasbilling'
event_types:
type: array
description: 'List of webhook event types to subscribe to.'
example:
- subscription.created
- invoice.paid
items:
type: string
description:
type: string
description: 'Optional description for this endpoint.'
example: 'Production receiver'
required:
- url
- event_types
'/api/v1/webhook-endpoints/{id}':
get:
summary: 'Retrieve a webhook endpoint'
operationId: retrieveAWebhookEndpoint
description: "Returns a single webhook endpoint, including its current delivery\nstatus and last-success / failure counters."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: webhook_endpoint
id: 13
url: 'https://example.com/webhooks/bfc53181-d647-36b2-9080-f9c2b76006f4'
description: 'Qui commodi incidunt iure odit.'
status: active
event_types: []
consecutive_failures: 0
last_success_at: null
last_failure_at: null
signing_secret_grace_ends_at: null
created_at: '2026-10-22T07:20:10+00:00'
updated_at: '2026-10-22T07:20:10+00:00'
properties:
object:
type: string
example: webhook_endpoint
id:
type: integer
example: 13
url:
type: string
example: 'https://example.com/webhooks/bfc53181-d647-36b2-9080-f9c2b76006f4'
description:
type: string
example: 'Qui commodi incidunt iure odit.'
status:
type: string
example: active
event_types:
type: array
example: []
consecutive_failures:
type: integer
example: 0
last_success_at:
type: string
example: null
nullable: true
last_failure_at:
type: string
example: null
nullable: true
signing_secret_grace_ends_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-10-22T07:20:10+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:10+00:00'
403:
description: 'Forbidden — API key lacks the required scope `webhooks:read`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:read'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:read'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- 'Webhook Endpoints'
patch:
summary: 'Update a webhook endpoint'
operationId: updateAWebhookEndpoint
description: "Updates the URL, description, subscribed events or active status of a\nwebhook endpoint. Setting `status=\"disabled\"` pauses deliveries without\nlosing the signing secret."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
object: webhook_endpoint
id: 14
url: 'https://example.com/webhooks/a4855dc5-0acb-33c3-b921-f4291f719ca0'
description: null
status: active
event_types: []
consecutive_failures: 0
last_success_at: null
last_failure_at: null
signing_secret_grace_ends_at: null
created_at: '2026-10-22T07:20:11+00:00'
updated_at: '2026-10-22T07:20:11+00:00'
properties:
object:
type: string
example: webhook_endpoint
id:
type: integer
example: 14
url:
type: string
example: 'https://example.com/webhooks/a4855dc5-0acb-33c3-b921-f4291f719ca0'
description:
type: string
example: null
nullable: true
status:
type: string
example: active
event_types:
type: array
example: []
consecutive_failures:
type: integer
example: 0
last_success_at:
type: string
example: null
nullable: true
last_failure_at:
type: string
example: null
nullable: true
signing_secret_grace_ends_at:
type: string
example: null
nullable: true
created_at:
type: string
example: '2026-10-22T07:20:11+00:00'
updated_at:
type: string
example: '2026-10-22T07:20:11+00:00'
403:
description: 'Forbidden — API key lacks the required scope `webhooks:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- 'Webhook Endpoints'
requestBody:
required: false
content:
application/json:
schema:
type: object
properties:
url:
type: string
description: 'New HTTPS URL.'
example: 'http://www.bailey.biz/quos-velit-et-fugiat-sunt-nihil-accusantium-harum.html'
event_types:
type: array
description: 'Replace the full set of subscribed event types.'
example:
- architecto
items:
type: string
description:
type: string
description: 'New description.'
example: 'Eius et animi quos velit et.'
status:
type: string
description: 'New status: "active" or "disabled".'
example: architecto
delete:
summary: 'Delete a webhook endpoint'
operationId: deleteAWebhookEndpoint
description: "Permanently removes a webhook endpoint. In-flight deliveries are\naborted; pending retries are discarded."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
204:
description: 'Webhook endpoint deleted.'
content:
application/json:
schema:
type: object
nullable: true
403:
description: 'Forbidden — API key lacks the required scope `webhooks:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- 'Webhook Endpoints'
parameters:
-
in: path
name: id
description: 'The webhook endpoint id.'
example: 16
required: true
schema:
type: integer
'/api/v1/webhook-endpoints/{id}/rotate':
post:
summary: 'Rotate the signing secret'
operationId: rotateTheSigningSecret
description: "Generates a new signing `secret` for the webhook endpoint and returns it\nin the response (one-time view). The previous secret continues to sign\ndeliveries for a 24-hour grace period so you can roll out the new one\nwithout dropping events."
parameters:
-
in: header
name: Idempotency-Key
description: ''
example: idem_01JXY...
schema:
type: string
responses:
200:
description: 'Rotated. The new `signing_secret` is returned **once** in plaintext. The previous secret remains valid for a grace window (see `signing_secret_grace_ends_at`).'
content:
application/json:
schema:
type: object
example:
object: webhook_endpoint
id: 1
url: 'https://app.example.com/webhooks/saasbilling'
description: 'Production receiver'
status: active
event_types:
- subscription.created
- invoice.paid
consecutive_failures: 0
last_success_at: '2026-05-20T09:00:00+00:00'
last_failure_at: null
signing_secret: whsec_NEW01JXY7Z3K2M5N6P8Q9R0S1T2
signing_secret_grace_ends_at: '2026-05-21T10:00:00+00:00'
created_at: '2026-05-01T00:00:00+00:00'
updated_at: '2026-05-20T10:00:00+00:00'
properties:
object:
type: string
example: webhook_endpoint
id:
type: integer
example: 1
url:
type: string
example: 'https://app.example.com/webhooks/saasbilling'
description:
type: string
example: 'Production receiver'
status:
type: string
example: active
event_types:
type: array
example:
- subscription.created
- invoice.paid
items:
type: string
consecutive_failures:
type: integer
example: 0
last_success_at:
type: string
example: '2026-05-20T09:00:00+00:00'
last_failure_at:
type: string
example: null
nullable: true
signing_secret:
type: string
example: whsec_NEW01JXY7Z3K2M5N6P8Q9R0S1T2
signing_secret_grace_ends_at:
type: string
example: '2026-05-21T10:00:00+00:00'
created_at:
type: string
example: '2026-05-01T00:00:00+00:00'
updated_at:
type: string
example: '2026-05-20T10:00:00+00:00'
403:
description: 'Forbidden — API key lacks the required scope `webhooks:write`.'
content:
application/json:
schema:
type: object
example:
error:
type: insufficient_scope
message: 'API key lacks required scope: webhooks:write'
doc_url: 'https://saasbilling.io/docs#insufficient_scope'
properties:
error:
type: object
properties:
type:
type: string
example: insufficient_scope
message:
type: string
example: 'API key lacks required scope: webhooks:write'
doc_url:
type: string
example: 'https://saasbilling.io/docs#insufficient_scope'
404:
description: 'Not Found — the referenced resource does not exist for this project.'
content:
application/json:
schema:
type: object
example:
error:
type: not_found
message: 'The requested resource was not found.'
doc_url: 'https://saasbilling.io/docs#not_found'
properties:
error:
type: object
properties:
type:
type: string
example: not_found
message:
type: string
example: 'The requested resource was not found.'
doc_url:
type: string
example: 'https://saasbilling.io/docs#not_found'
tags:
- 'Webhook Endpoints'
parameters:
-
in: path
name: id
description: 'The webhook endpoint id.'
example: 16
required: true
schema:
type: integer
/api/v1/health:
get:
summary: 'Liveness probe'
operationId: livenessProbe
description: "Returns `200 {\"status\":\"ok\"}` whenever the API process is up and serving\ntraffic. It requires no authentication and touches no datastore, so it\nlets you tell apart two failure modes that otherwise look identical: a\n`200` here next to a `5xx` on an authenticated call means the outage is\ndownstream (database / cache), not your API key; no response at all means\nthe service itself is unreachable."
parameters: []
responses:
200:
description: ''
content:
application/json:
schema:
type: object
example:
status: ok
properties:
status:
type: string
example: ok
tags:
- Health
security: []