API reference

Billing API

Plans, credit balances and statements

HTTP reference. Generated from the backend contract. Browse endpoints below or download OpenAPI.

This group has 6 operations, generated from api/openapi.yaml. Filter by path, action, or authentication. Each endpoint lists its inputs; expand it for a request template and response codes. Templates need your own values and a schema-valid body.

Replace uppercase placeholders with your values. For a JSON request, save a body matching the schema in request.json. Cookie examples use a local cookie jar from an authenticated session.

Plans, credit balances and statements

GET/api/billing/balance

Get Billing Balance

The calling project's source-aware balance snapshot. All millicredit amounts are canonical decimal strings. It is a snapshot, not authority to pay; rails_available is false while card, tax and cash-out rail are unavailable.

Authentication
projectBearer OR dashboardSession
Request body
none
Operation ID
getBillingBalance

Parameters

NameSend inRequired?Description
ScreenRig-ProjectheaderoptionalDeprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange.
Request template and responses
shell · request template
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/balance'

Responses

StatusMeaningBody and headers
200Balance snapshot.BillingBalance (application/json)
default

Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value.

Problem (application/problem+json)
GET/api/billing/statement

Get Billing Statement

Stable cursor pagination over immutable posted journal entries for the calling project. Holds are not posted entries. Corrections link to the original event instead of rewriting it. There is no arbitrary grant or payout mutation on this surface.

Authentication
projectBearer OR dashboardSession
Request body
none
Operation ID
getBillingStatement

Parameters

NameSend inRequired?Description
ScreenRig-ProjectheaderoptionalDeprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange.
cursorqueryoptional
limitqueryoptional
Request template and responses
shell · request template
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/statement'

Responses

StatusMeaningBody and headers
200Statement page.BillingStatement (application/json)
400invalid_request - limit is out of range.Problem (application/problem+json)
default

Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value.

Problem (application/problem+json)
GET/api/billing/usage

Get Billing Usage

The calling project's use for one UTC month, with the bytes served and received (never charged), the storage charged above the free storage of the payer's plan (allowance.storage_bytes), and the API requests, billed project stream events and webhook delivery attempts counted against the free 10,000 a month. A project paying for itself reads its own pool.

A project another payer pays for shares that payer's pool with the payer's other projects; it reads its own share, and payer names the payer. The payer's pool total stays on the payer's billing reads.

Authentication
projectBearer OR dashboardSession
Request body
none
Operation ID
getBillingUsage

Parameters

NameSend inRequired?Description
ScreenRig-ProjectheaderoptionalDeprecated for one release. Must be consistent with the JWT projects grants; adds Deprecation and Warning headers. No token exchange.
periodqueryoptionalUTC month in YYYY-MM form; defaults to the current month.
Request template and responses
shell · request template
curl --request GET --header 'Authorization: Bearer [SCREENRIG_TOKEN]' --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/billing/usage'

Responses

StatusMeaningBody and headers
200Project usage.ProjectBillingUsage (application/json); headers: Cache-Control
400invalid_request - period is not a UTC month in YYYY-MM form.Problem (application/problem+json)
default

Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value.

Problem (application/problem+json)
GET/api/organizations/{organization_id}/app-store/offer

Get App Store Offer

What an organization may buy through the App Store in the screenRIG iPhone and iPad app (bundle ai.screenrig.app), and the account token its purchases carry.

Requires a designated admin of the live organization (the person who manages its plan and billing); staff and the admins of the reseller the organization is delegated to may read the offer without products or token, and anyone else answers 404 not_found.

An organization delegated to a reseller lists no products: its reseller pays for its plan. account_token is a random UUID the server creates once per organization and keeps: the app passes it to StoreKit as appAccountToken, so every transaction and server notification names its organization.

It is the all-zero UUID for a reader who cannot buy. products lists the plans the caller may buy now, one App Store product each: plus, education (only while education_eligible is true) and premium. education_eligible is true when staff verified a K-12 school or district, or when the admin reading the offer has a verified account email address at a K-12 school domain (education_eligibility_source school_email): opening the offer, or recording a transaction, as such an admin makes the organization eligible once, writes an organization audit entry naming the matched domain, and includes Education in the same offer.

A staff decision, either way, is final and automatic eligibility never overrides it.

A plan the organization already holds through another source (staff, invoicing, another subscription) at the same or a higher level is not offered, except to the person whose App Store subscription holds it, who may change plans within the subscription group. can_purchase is true when products is not empty. subscription is the organization's App Store subscription, or null.

Response is private and no-store.

Authentication
dashboardSession
Request body
none
Operation ID
getAppStoreOffer

Parameters

NameSend inRequired?Description
organization_idpathrequired
Request template and responses
shell · request template
curl --request GET --cookie cookies.txt --cookie-jar cookies.txt 'https://screenrig.ai/api/organizations/ORGANIZATION_ID/app-store/offer'

Responses

StatusMeaningBody and headers
200The organization's App Store plan offer.AppStoreOffer (application/json); headers: Cache-Control
default

Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value.

Problem (application/problem+json)
POST/api/organizations/{organization_id}/app-store/transactions

Record App Store Transaction

Records one StoreKit 2 signed transaction (Transaction.jwsRepresentation) bought for the organization and puts the organization on its plan. Requires a designated admin of the live organization, same-origin cookie authentication and, for JWT sessions, the CSRF header.

The server verifies the JWS with its x5c chain to Apple Root CA - G3 (outside production also Xcode's StoreKit testing root), the bundle id ai.screenrig.app, the product id, the environment (Production on production only; Sandbox everywhere, because App Review and TestFlight buy with sandbox accounts against production; Xcode outside production only) and that appAccountToken equals the organization's account_token (getAppStoreOffer); nothing in the request names who paid.

The plan follows the product of the newest transaction of the subscription: a higher plan applies at once and a lower one when billing's next UTC month boundary reaches it, as for any plan change. Sending a transaction again, or an older one, changes nothing and answers the subscription as it stands; an expired transaction is recorded and changes no plan.

The app finishes the transaction after a 2xx or a definitive 4xx problem. Problems: app_store_transaction_invalid (422), app_store_account_mismatch (409), education_not_eligible (403), app_store_plan_conflict (409), billed_by_reseller (409, the organization is delegated to a reseller, which pays for its plan).

Authentication
dashboardSession
Request body
AppStoreTransactionRequest
Operation ID
recordAppStoreTransaction

Parameters

NameSend inRequired?Description
organization_idpathrequired

Request body fields

FieldDescription
signed_transaction (required, string)The transaction's StoreKit 2 jwsRepresentation, exactly as StoreKit returned it. minLength: 1 maxLength: 65536
Request template and responses
shell · request template
curl --request POST --cookie cookies.txt --cookie-jar cookies.txt --header 'Content-Type: application/json' --data '@request.json' 'https://screenrig.ai/api/organizations/ORGANIZATION_ID/app-store/transactions'

Responses

StatusMeaningBody and headers
200The App Store subscription after the transaction.AppStoreSubscription (application/json); headers: Cache-Control
default

Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value.

Problem (application/problem+json)
POST/api/app-store/notifications

Receive App Store Notification

Apple's App Store Server Notifications V2 (the signedPayload body), the URL set in App Store Connect for the production and sandbox environments. Public: the signed payload is the credential, verified the way recordAppStoreTransaction verifies a transaction, together with the signed transaction and renewal info inside it.

SUBSCRIBED, DID_RENEW, DID_CHANGE_RENEWAL_PREF and DID_CHANGE_RENEWAL_STATUS (and OFFER_REDEEMED, RENEWAL_EXTENDED and REFUND_REVERSED) apply the newest transaction: a renewal extends the subscription, an upgrade moves the plan at once and a downgrade when the lower product is renewed at period end.

EXPIRED, GRACE_PERIOD_EXPIRED, REFUND and REVOKE end the subscription and remove the plan it set, as billing removes any plan: at the next UTC month boundary, taking nothing already granted back. A plan staff or invoicing put on the organization since is never removed.

Any other type, a notification for a product that is not a plan, and one that names no organization are acknowledged and logged. 200 acknowledges. A payload that does not verify is a 422 app_store_transaction_invalid problem; a failure on the server's side is a 5xx, which Apple sends again. Limited per client address.

Authentication
none
Request body
AppStoreNotificationRequest
Operation ID
receiveAppStoreNotification

Request body fields

FieldDescription
signedPayload (required, string)Apple's signed notification (a JWS). minLength: 1
Request template and responses
shell · request template
curl --request POST --header 'Content-Type: application/json' --data '@request.json' 'https://api.screenrig.ai/api/app-store/notifications'

Responses

StatusMeaningBody and headers
200The notification was acknowledged.AppStoreNotificationAcknowledgement (application/json); headers: Cache-Control
422app_store_transaction_invalid — the payload is not a valid App Store notification for this server.Problem (application/problem+json)
429

Stable rate_limited problem. Retry only after the authoritative Retry-After interval. Fixed-window denials also include the applicable RateLimit-Policy and RateLimit members; concurrency and byte-budget denials omit those optional disclosures.

Problem (application/problem+json); headers: Retry-After, RateLimit-Policy, RateLimit
default

Stable RFC 9457 problem response. A 421 server_draining carries Retry-After of 1 second, a 500 not_ready or dependency_unavailable carries 5, and a 500 manifest_degraded carries 60, unless the handler set a more specific value.

Problem (application/problem+json)