Docs menu

Billing API reference

Buy credit packs (hosted checkout).

View as Markdown

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

Example 200 response
{
  "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.

FieldTypeDescription
packrequiredstring
successUrlstringWhere 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
returnUrlstringWhere “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
Example request body
{
  "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

Example 201 response
{
  "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

Example 200 response
{
  "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

Example 200 response
{
  "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

Example 200 response
{
  "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.

FieldTypeDescription
planrequiredstringOne of: "pro", "agency", "scale"
successUrlstringWhere 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
returnUrlstringWhere “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
Example request body
{
  "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

Example 201 response
{
  "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.

FieldTypeDescription
planrequiredstringOne of: "pro", "agency", "scale"
Example request body
{
  "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

Example 200 response
{
  "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

Example 200 response
{
  "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

Example 200 response
{
  "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.

FieldTypeDescription
returnUrlstringWhere “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
Example request body
{
  "returnUrl": "https://example.com"
}

Responses

200 The portal link.

Schema BillingPortalSession (application/json).

Headers: X-Request-Id , RateLimit , RateLimit-Policy

Example 200 response
{
  "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