Billing API reference
Buy credit packs (hosted checkout).
List credit packs
GET /billing/packs
Auth: API key with scope billing:read or billing:write, or access token.
Requires billing:read or billing:write (not default scopes). The one-time credit packs on sale, with list prices in cents; taxes are applied at checkout by the payment provider (merchant of record). Empty while web payments are switched off. Any key environment may list.
Credits. Free: no credits.
Responses
200 The packs on sale.
Schema BillingPackList (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"data": [
{
"id": "credits_15",
"name": "Storeshot API: 15 credits",
"credits": 15,
"priceAmount": 399,
"currency": "usd"
}
]
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: see details.reason.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Start a checkout
POST /billing/checkout
Auth: API key with scope billing:write, or access token.
Requires billing:write (not a default scope) and a live credential. Creates a hosted checkout for one pack and returns its url: send the buyer there. Credits are added to purchased once the payment succeeds (poll GET /billing/checkouts/{id}); a refund takes back what is still unused. Checkouts expire after 24 hours. At most 10 per account per hour. Send an Idempotency-Key so a retried request never opens two checkouts.
When to use. Needs the non-default billing:write scope: to hand your human a payment url for a credit pack. Without it, give them the details.topUpUrl of the 402. Never pay yourself.
Credits. Free: no credits. Payments happen on the hosted page, never through the API. A paid checkout adds the pack’s credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idempotency-key | header | string | 1–255 printable ASCII characters. Retrying with the same key and body returns the original checkout (Idempotent-Replayed: true) instead of creating another. |
Request body application/json
Required. Schema CreateBillingCheckoutRequest.
| Field | Type | Description |
|---|---|---|
packrequired | string | |
successUrl | string | Where the buyer lands after paying. Default: a static confirmation page. https:// on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters.Format: uri |
returnUrl | string | Where “back” on the checkout page leads. Default: the credits page. https:// on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters.Format: uri |
{
"pack": "credits_15",
"successUrl": "https://example.com",
"returnUrl": "https://example.com"
}Responses
201 Created (or, with Idempotent-Replayed: true, the checkout created earlier with this key and body).
Schema BillingCheckout (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Idempotent-Replayed
{
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"status": "open",
"pack": "credits_15",
"credits": 0,
"priceAmount": 399,
"currency": "usd",
"url": "https://buy.polar.sh/polar_c_abc123",
"expiresAt": "2026-10-29T12:00:00.000Z",
"createdAt": "2026-10-29T12:00:00.000Z",
"paidAt": "2026-10-29T12:00:00.000Z",
"creditsGranted": 0,
"creditsReversed": 0,
"kind": "subscription",
"plan": "pro",
"planName": "Storeshot API Pro",
"subscriptionStatus": "active"
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: insufficient_scope, live_key_required or billing_account_flagged (details.reason).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
409 IDEMPOTENCY_CONFLICT: the key was used for a different body, or that checkout is still being created (details.checkoutId).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
413 PAYLOAD_TOO_LARGE.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
429 RATE_LIMITED: request rate (see details.scope), or 10 checkouts in the last hour (details.policy billing_checkouts).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
503 BILLING_UNAVAILABLE: disabled, not_configured or provider_error (details.reason). Nothing was charged.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Get a checkout
GET /billing/checkouts/{id}
Auth: API key with scope billing:read or billing:write, or access token.
Requires billing:read or billing:write and a live credential. The checkout status, derived from the payment provider: open, processing, paid, partially_refunded, refunded, expired or failed, with the credits granted and reversed so far. Only the account that created it can read it. {id} is our checkout id or the payment provider’s checkout id (the {CHECKOUT_ID} placeholder of successUrl), so a success page can look the checkout up straight from its URL. Plan checkouts also return planName and subscriptionStatus.
Credits. Free: no credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
id required | path | string | Our checkout id (id) or the payment provider’s checkout id (what {CHECKOUT_ID} in your successUrl becomes); our id wins if both match. An unknown, foreign or malformed id is 404. |
Responses
200 The checkout.
Schema BillingCheckout (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"status": "open",
"pack": "credits_15",
"credits": 0,
"priceAmount": 399,
"currency": "usd",
"url": "https://buy.polar.sh/polar_c_abc123",
"expiresAt": "2026-10-29T12:00:00.000Z",
"createdAt": "2026-10-29T12:00:00.000Z",
"paidAt": "2026-10-29T12:00:00.000Z",
"creditsGranted": 0,
"creditsReversed": 0,
"kind": "subscription",
"plan": "pro",
"planName": "Storeshot API Pro",
"subscriptionStatus": "active"
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: see details.reason.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
404 NOT_FOUND: no such resource for this caller.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
List plans
GET /billing/plans
Auth: API key with scope billing:read or billing:write, or access token.
Requires billing:read or billing:write (not default scopes). The monthly plans on sale, with list prices in cents (taxes are applied at checkout) and the limits each plan raises. Each paid month resets the subscription balance to the plan credits; subscription credits are used before starter and purchased credits and expire at renewal. Empty while plans are switched off. Any key environment may list.
Credits. Free: no credits.
Responses
200 The plans on sale.
Schema BillingPlanList (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"data": [
{
"id": "pro",
"name": "Storeshot API Pro",
"credits": 60,
"priceAmount": 1200,
"currency": "usd",
"interval": "month",
"limits": {}
}
]
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: see details.reason.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Get the plan of this account
GET /billing/subscription
Auth: API key with scope billing:read or billing:write, or access token.
Requires billing:read or billing:write. The tier whose limits apply now (free without a plan), those limits, and the latest subscription with its subscription balance. Any key environment may read.
Credits. Free: no credits.
Responses
200 The plan state.
Schema BillingSubscriptionState (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"plan": "free",
"limits": {
"requestsPerMinutePerKey": 0,
"requestsPerMinutePerUser": 0,
"concurrentGenerations": 0,
"dailyGenerations": 0,
"maxStoredUploads": 0,
"maxStoredUploadMegabytes": 0
},
"subscription": {
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"plan": "pro",
"status": "active",
"cancelAtPeriodEnd": true,
"currentPeriodStart": "2026-10-29T12:00:00.000Z",
"currentPeriodEnd": "2026-10-29T12:00:00.000Z",
"pendingPlan": "pro",
"creditsPerPeriod": 0,
"subscriptionCredits": 0
}
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: see details.reason.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Start a plan checkout
POST /billing/subscription/checkout
Auth: API key with scope billing:write, or access token.
Requires billing:write and a live credential. Creates a hosted checkout for a monthly plan and returns its url. Once the first payment succeeds the plan applies and the subscription balance is set to the plan credits (poll GET /billing/subscription). One plan per account: an account with a plan changes it instead. Asking again for the same plan within 15 minutes returns the open checkout; another plan replaces it. At most 10 checkouts per account per hour (packs included). Send an Idempotency-Key so a retried request never opens two checkouts.
Credits. Free: no credits. Payments happen on the hosted page, never through the API. Each paid month resets the plan credits.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
idempotency-key | header | string | 1–255 printable ASCII characters. Retrying with the same key and body returns the original checkout (Idempotent-Replayed: true) instead of creating another. |
Request body application/json
Required. Schema CreateSubscriptionCheckoutRequest.
| Field | Type | Description |
|---|---|---|
planrequired | string | One of: "pro", "agency", "scale" |
successUrl | string | Where the buyer lands after paying. Default: a static confirmation page. https:// on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters.Format: uri |
returnUrl | string | Where “back” on the checkout page leads. Default: the credits page. https:// on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters.Format: uri |
{
"plan": "pro",
"successUrl": "https://example.com",
"returnUrl": "https://example.com"
}Responses
201 Created (or, with Idempotent-Replayed: true, the open checkout created earlier for this plan or this key).
Schema BillingCheckout (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Idempotent-Replayed
{
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"status": "open",
"pack": "credits_15",
"credits": 0,
"priceAmount": 399,
"currency": "usd",
"url": "https://buy.polar.sh/polar_c_abc123",
"expiresAt": "2026-10-29T12:00:00.000Z",
"createdAt": "2026-10-29T12:00:00.000Z",
"paidAt": "2026-10-29T12:00:00.000Z",
"creditsGranted": 0,
"creditsReversed": 0,
"kind": "subscription",
"plan": "pro",
"planName": "Storeshot API Pro",
"subscriptionStatus": "active"
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: insufficient_scope, live_key_required or billing_account_flagged (details.reason).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
409 BILLING_STATE_CONFLICT (subscription_exists, checkout_pending) or IDEMPOTENCY_CONFLICT.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
413 PAYLOAD_TOO_LARGE.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
429 RATE_LIMITED: request rate (see details.scope), or 10 checkouts in the last hour (details.policy billing_checkouts).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
503 BILLING_UNAVAILABLE: disabled, not_configured or provider_error (details.reason). Nothing changed.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Change the plan
POST /billing/subscription/change
Auth: API key with scope billing:write, or access token.
Requires billing:write and a live credential. A higher plan applies at once: the prorated difference is charged now and, once paid, the subscription balance grows by the extra credits for the rest of the period. A lower plan starts at the next renewal (pendingPlan). Asking for the current plan while a downgrade is pending cancels the downgrade.
Credits. Free: no credits. Payments happen on the hosted page, never through the API.
Request body application/json
Required. Schema ChangeSubscriptionRequest.
| Field | Type | Description |
|---|---|---|
planrequired | string | One of: "pro", "agency", "scale" |
{
"plan": "agency"
}Responses
200 The plan state after the change (credits move only when the provider reports payment).
Schema BillingSubscriptionState (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"plan": "free",
"limits": {
"requestsPerMinutePerKey": 0,
"requestsPerMinutePerUser": 0,
"concurrentGenerations": 0,
"dailyGenerations": 0,
"maxStoredUploads": 0,
"maxStoredUploadMegabytes": 0
},
"subscription": {
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"plan": "pro",
"status": "active",
"cancelAtPeriodEnd": true,
"currentPeriodStart": "2026-10-29T12:00:00.000Z",
"currentPeriodEnd": "2026-10-29T12:00:00.000Z",
"pendingPlan": "pro",
"creditsPerPeriod": 0,
"subscriptionCredits": 0
}
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: insufficient_scope or live_key_required (details.reason).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
409 BILLING_STATE_CONFLICT: no_subscription, same_plan, canceling or past_due (details.reason). Nothing was charged.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
413 PAYLOAD_TOO_LARGE.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
503 BILLING_UNAVAILABLE: disabled, not_configured or provider_error (details.reason). Nothing changed.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Cancel the plan at the period end
POST /billing/subscription/cancel
Auth: API key with scope billing:write, or access token.
Requires billing:write and a live credential. The plan, its limits and its credits stay until the period ends; nothing renews after that. Works while plans are switched off, so a subscriber can always stop paying.
Credits. Free: no credits. Payments happen on the hosted page, never through the API.
Responses
200 The plan state after the change (credits move only when the provider reports payment).
Schema BillingSubscriptionState (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"plan": "free",
"limits": {
"requestsPerMinutePerKey": 0,
"requestsPerMinutePerUser": 0,
"concurrentGenerations": 0,
"dailyGenerations": 0,
"maxStoredUploads": 0,
"maxStoredUploadMegabytes": 0
},
"subscription": {
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"plan": "pro",
"status": "active",
"cancelAtPeriodEnd": true,
"currentPeriodStart": "2026-10-29T12:00:00.000Z",
"currentPeriodEnd": "2026-10-29T12:00:00.000Z",
"pendingPlan": "pro",
"creditsPerPeriod": 0,
"subscriptionCredits": 0
}
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: insufficient_scope or live_key_required (details.reason).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
409 BILLING_STATE_CONFLICT: no_subscription (details.reason). Nothing was charged.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
503 BILLING_UNAVAILABLE: disabled, not_configured or provider_error (details.reason). Nothing changed.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Keep the plan (undo a cancel)
POST /billing/subscription/uncancel
Auth: API key with scope billing:write, or access token.
Requires billing:write and a live credential. Undoes a cancel before the period ends; the plan renews as before.
Credits. Free: no credits. Payments happen on the hosted page, never through the API.
Responses
200 The plan state after the change (credits move only when the provider reports payment).
Schema BillingSubscriptionState (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"plan": "free",
"limits": {
"requestsPerMinutePerKey": 0,
"requestsPerMinutePerUser": 0,
"concurrentGenerations": 0,
"dailyGenerations": 0,
"maxStoredUploads": 0,
"maxStoredUploadMegabytes": 0
},
"subscription": {
"id": "7d0e1a52-3c4b-4f6e-9a8d-2b1c0f9e8a7d",
"plan": "pro",
"status": "active",
"cancelAtPeriodEnd": true,
"currentPeriodStart": "2026-10-29T12:00:00.000Z",
"currentPeriodEnd": "2026-10-29T12:00:00.000Z",
"pendingPlan": "pro",
"creditsPerPeriod": 0,
"subscriptionCredits": 0
}
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: insufficient_scope or live_key_required (details.reason).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
409 BILLING_STATE_CONFLICT: no_subscription (details.reason). Nothing was charged.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
503 BILLING_UNAVAILABLE: disabled, not_configured or provider_error (details.reason). Nothing changed.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
Open the billing portal
POST /billing/portal
Auth: API key with scope billing:write, or access token.
Requires billing:write and a live credential. Returns a link to the payment provider portal (invoices, payment method, cancel). mode: session is signed in as this account; it is offered only when the payment account belongs to this account email. Otherwise mode: email_login asks for the billing email and sends a one-time code.
Credits. Free: no credits. Payments happen on the hosted page, never through the API.
Request body application/json
Optional. Schema CreatePortalSessionRequest.
| Field | Type | Description |
|---|---|---|
returnUrl | string | Where “back” in the portal leads. Default: the credits page. https:// on storeshot.art, a storeshot.art subdomain or this API host; at most 2048 characters.Format: uri |
{
"returnUrl": "https://example.com"
}Responses
200 The portal link.
Schema BillingPortalSession (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
{
"url": "https://example.com",
"expiresAt": "2026-10-29T12:00:00.000Z",
"mode": "session"
} 400 INVALID_INPUT. A credential sent in the query string is refused before authentication, without RateLimit headers.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
401 UNAUTHORIZED: missing, malformed, unknown, revoked or expired credential.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
403 FORBIDDEN: insufficient_scope or live_key_required (details.reason).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
409 BILLING_STATE_CONFLICT: no_customer (details.reason). Nothing was charged.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy
413 PAYLOAD_TOO_LARGE.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id
429 RATE_LIMITED: the request rate of the API key (details.scope key) or of the account (user) is exhausted for this window, or too many failed authentication attempts came from this IP (no details.scope and no RateLimit headers). Retry after Retry-After seconds.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
503 BILLING_UNAVAILABLE: disabled, not_configured or provider_error (details.reason). Nothing changed.
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy , Retry-After
default Error (see ErrorEnvelope and /v1/docs/errors).
Schema ErrorEnvelope (application/json).
Headers: X-Request-Id , RateLimit , RateLimit-Policy