Ir al contenido

Billing

Esta página aún no está disponible en tu idioma.

The billing resource exposes your tenant’s subscription state, plan catalog, usage counters, and write endpoints for initiating Stripe Checkout sessions or opening the Stripe Customer Portal. Writes use the owner-gated billing routes and require an API key with billing.write. API access requires the Business plan (scale) and an environment where the public API is enabled.

Stripe IDs not exposed, internal Stripe price IDs, customer IDs, and session IDs are not surfaced. Use the url field from checkout/portal responses to redirect the user.

The API retains these plan codes. Send the code in requests and use the name for display.

  • starter: Standard. $29 a month. 1 person, 1 store, 75 new tickets a month (imported tickets don’t count). No API access.
  • pro: Pro. $79 a month. Up to 8 people, 1 store, unlimited tickets. No API access.
  • scale: Business. $149 a month for the first store and $79 for each extra store. Unlimited people and unlimited tickets, and every store gets everything in Business. API access.

These USD list prices are from the US public pricing feed. Prices are monthly. Pricing can vary by country and account offer. Founders pricing is for the first 50 shops that subscribe after the beta in eligible regions while the founders window is open: $59/month for Pro, or $99/month for the first Business store plus $59 for each extra store; Standard has no founders price. Read the feed’s foundersWindow for current availability.

Self-serve trials default to 14 days on Business (scale), with no credit card. Trial length and plan can be configured; use the returned trial state for the account’s actual dates. An invited beta grant provides 365 days on Business free from account creation, then half of list price on the chosen plan for the life of the account.

More than one store needs Business. All billable locations use the workspace’s plan; warehouses are not billed as stores. There are no customer-count caps in the plan limits below. null means no fixed numeric cap, not -1.


GET /api/v1/billing

Returns the tenant’s subscription state: current plan, effective plan, entitlements, trial status, and the active subscription summary. If billing is not configured, the response can have configured: false and null plan/subscription fields. A failure to read billing authority can return 503; handle it as unavailable rather than treating the account as unconfigured.

Scope required: billing.read

Terminal window
curl "https://app.benchkey.com/api/v1/billing" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch("https://app.benchkey.com/api/v1/billing", {
headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },
});
const billing = await res.json();

Example for an active Business subscription. Dates and usage counts on this page are illustrative.

{
"object": "billing",
"configured": true,
"plan": "scale",
"status": "active",
"effective_plan": "scale",
"grandfathered": false,
"subscription": {
"plan": "scale",
"status": "active",
"current_period_end": "2026-10-24T00:00:00.000Z",
"cancel_at_period_end": false
},
"trial": null,
"entitlements": {
"plan": "scale",
"status": "active",
"grandfathered": false,
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": true,
"buy_sell": true,
"time_clock": true,
"integrations": true,
"automation": true,
"reports_advanced": true,
"ai_features": true,
"multi_location": true,
"customer_plans": true,
"campaigns": true,
"customer_portal": true,
"compensation": true,
"b2b_asset_tracking": true,
"mailin_shipping": true,
"api_access": true,
"shift_scheduling": true
}
}
}
FieldTypeDescription
objectstringAlways "billing"
configuredbooleanWhether billing is set up for this tenant
planstring|nullThe raw subscription plan key
statusstring|nullStored subscription status, for example active, trialing, past_due, or canceled
effective_planstring|nullThe plan whose limits are actually enforced (may differ from plan if grandfathered)
grandfatheredbooleanWhether the tenant has a grandfathered entitlement override; this is separate from founders pricing
subscriptionobject|nullActive subscription summary; null if no subscription
trialobject|nullSynthetic trial metadata (onTrial, expired, endsAt, daysLeft, plan); null for other subscription rows, including Stripe-managed trials
entitlementsobjectResolved entitlements including feature flags

GET /api/v1/billing/usage

Returns usage counters and effective limits. tickets excludes hidden, soft-deleted, and billing-only tickets. locations counts active locations, including warehouses, so it is not a billable-store total. Pro’s users limit is 8 and its locations limit is 1; Business has no numeric users cap.

Scope required: billing.read

Terminal window
curl "https://app.benchkey.com/api/v1/billing/usage" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch("https://app.benchkey.com/api/v1/billing/usage", {
headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },
});
const usage = await res.json();
{
"object": "billing_usage",
"effective_plan": "scale",
"limits": {
"users": null,
"locations": null
},
"usage": {
"users": 5,
"locations": 2,
"tickets": 1842,
"invoices": 1200,
"leads": 38
},
"usage_status": {
"users": {
"used": 5,
"limit": null,
"remaining": null,
"within_limit": true
},
"locations": {
"used": 2,
"limit": null,
"remaining": null,
"within_limit": true
},
"tickets": {
"used": 1842,
"limit": null,
"remaining": null,
"within_limit": true
},
"invoices": {
"used": 1200,
"limit": null,
"remaining": null,
"within_limit": true
},
"leads": {
"used": 38,
"limit": null,
"remaining": null,
"within_limit": true
}
}
}
FieldTypeDescription
objectstringAlways "billing_usage"
effective_planstring|nullThe plan whose limits are being measured
limitsobjectEnforced numeric caps for users and locations; null means no fixed cap
usageobjectCounts for users, locations, tickets, invoices, and leads; a count can be null when unavailable
usage_statusobjectPer-dimension used, limit, remaining, and within_limit; uncapped dimensions have limit: null, remaining: null, and within_limit: true

GET /api/v1/billing/plans

Returns the server-side plan catalog, pricing tiers, limits, and feature flags. Use this to build an upgrade UI without hard-coding plan details in your client.

Scope required: billing.read

Terminal window
curl "https://app.benchkey.com/api/v1/billing/plans" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch("https://app.benchkey.com/api/v1/billing/plans", {
headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },
});
const { plans } = await res.json();
{
"object": "billing_plan_catalog",
"configured": true,
"plans": [
{
"key": "starter",
"name": "Standard",
"price": {
"monthlyCents": 2900,
"currency": "usd",
"interval": "month",
"display": "$29/mo"
},
"limits": {
"users": 1,
"locations": 1
},
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": false,
"buy_sell": false,
"time_clock": false,
"integrations": false,
"automation": false,
"reports_advanced": false,
"ai_features": false,
"multi_location": false,
"customer_plans": false,
"campaigns": false,
"customer_portal": false,
"compensation": false,
"b2b_asset_tracking": false,
"mailin_shipping": false,
"api_access": false,
"shift_scheduling": false
}
},
{
"key": "pro",
"name": "Pro",
"price": {
"monthlyCents": 7900,
"currency": "usd",
"interval": "month",
"display": "$79/mo"
},
"founders": {
"monthlyCents": 5900,
"currency": "usd",
"interval": "month",
"display": "$59/mo"
},
"limits": {
"users": 8,
"locations": 1
},
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": true,
"buy_sell": true,
"time_clock": true,
"integrations": true,
"automation": true,
"reports_advanced": true,
"ai_features": true,
"multi_location": false,
"customer_plans": true,
"campaigns": true,
"customer_portal": true,
"compensation": true,
"b2b_asset_tracking": false,
"mailin_shipping": false,
"api_access": false,
"shift_scheduling": true
}
},
{
"key": "scale",
"name": "Business",
"price": {
"monthlyCents": 14900,
"currency": "usd",
"interval": "month",
"display": "$149/mo"
},
"founders": {
"monthlyCents": 9900,
"currency": "usd",
"interval": "month",
"display": "$99/mo"
},
"limits": {
"users": null,
"locations": null
},
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": true,
"buy_sell": true,
"time_clock": true,
"integrations": true,
"automation": true,
"reports_advanced": true,
"ai_features": true,
"multi_location": true,
"customer_plans": true,
"campaigns": true,
"customer_portal": true,
"compensation": true,
"b2b_asset_tracking": true,
"mailin_shipping": true,
"api_access": true,
"shift_scheduling": true
}
}
]
}

This example shows the US catalog while founders pricing is available to the account. price.monthlyCents and founders.monthlyCents are integer cents, not whole dollars. founders is optional. Feature flags come from the server; api_access and multi_location are true only for Business; shift_scheduling is true for Pro and Business. There is no separate webhooks flag in this catalog.

Internal Stripe price IDs are stripped. This v1 catalog exposes key, name, price, optional founders, limits, and features. It does not expose the public pricing feed’s foundersWindow field. The v1 checkout endpoint does not accept an interval field.


GET /api/v1/billing/limits

Returns the enforced limits for the tenant’s effective plan, plus the tenant-aware plan catalog. Catalog limits are per-plan base limits: Pro allows 8 people and one store. Grandfathered entitlement overrides can also affect the effective plan.

Scope required: billing.read

Terminal window
curl "https://app.benchkey.com/api/v1/billing/limits" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
{
"object": "billing_limits",
"effective_plan": "scale",
"limits": {
"users": null,
"locations": null
},
"plans": [
{
"key": "starter",
"name": "Standard",
"price": {
"monthlyCents": 2900,
"currency": "usd",
"interval": "month",
"display": "$29/mo"
},
"limits": {
"users": 1,
"locations": 1
},
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": false,
"buy_sell": false,
"time_clock": false,
"integrations": false,
"automation": false,
"reports_advanced": false,
"ai_features": false,
"multi_location": false,
"customer_plans": false,
"campaigns": false,
"customer_portal": false,
"compensation": false,
"b2b_asset_tracking": false,
"mailin_shipping": false,
"api_access": false,
"shift_scheduling": false
}
},
{
"key": "pro",
"name": "Pro",
"price": {
"monthlyCents": 7900,
"currency": "usd",
"interval": "month",
"display": "$79/mo"
},
"founders": {
"monthlyCents": 5900,
"currency": "usd",
"interval": "month",
"display": "$59/mo"
},
"limits": {
"users": 8,
"locations": 1
},
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": true,
"buy_sell": true,
"time_clock": true,
"integrations": true,
"automation": true,
"reports_advanced": true,
"ai_features": true,
"multi_location": false,
"customer_plans": true,
"campaigns": true,
"customer_portal": true,
"compensation": true,
"b2b_asset_tracking": false,
"mailin_shipping": false,
"api_access": false,
"shift_scheduling": true
}
},
{
"key": "scale",
"name": "Business",
"price": {
"monthlyCents": 14900,
"currency": "usd",
"interval": "month",
"display": "$149/mo"
},
"founders": {
"monthlyCents": 9900,
"currency": "usd",
"interval": "month",
"display": "$99/mo"
},
"limits": {
"users": null,
"locations": null
},
"features": {
"tickets": true,
"customers": true,
"invoices": true,
"inventory_basic": true,
"pos_quicksale": true,
"appointments": true,
"kiosk": true,
"reports_basic": true,
"notifications": true,
"settings": true,
"payment_integrations": true,
"configure_chat": true,
"leads": true,
"buy_sell": true,
"time_clock": true,
"integrations": true,
"automation": true,
"reports_advanced": true,
"ai_features": true,
"multi_location": true,
"customer_plans": true,
"campaigns": true,
"customer_portal": true,
"compensation": true,
"b2b_asset_tracking": true,
"mailin_shipping": true,
"api_access": true,
"shift_scheduling": true
}
}
]
}

GET /api/v1/billing/location-plans

Returns stored seat assignments for active locations. Deactivated locations are filtered out and do not affect counts. This is a read-only summary, not support for choosing different plans per location: every billable location uses the workspace plan. Accounts without seat rows return an empty seats array, zero counts, and highest_plan: null.

Scope required: billing.read

Terminal window
curl "https://app.benchkey.com/api/v1/billing/location-plans" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
{
"object": "billing_location_plans",
"highest_plan": "scale",
"counts": {
"pro": 0,
"scale": 2
},
"seats": [
{
"location_id": 1,
"plan": "scale"
},
{
"location_id": 2,
"plan": "scale"
}
]
}
FieldTypeDescription
highest_planstring|nullThe highest plan among all active seat assignments (Business "scale" > Pro "pro")
countsobjectCount of active seats per plan key
seatsarrayPer-location seat assignments (active locations only)

POST /api/v1/billing/checkout

For a tenant starting a subscription, creates a Stripe Checkout session and returns its redirect URL. An existing subscription is never redirected to a Portal plan switcher. A refused plan change returns 409 (including plan_change_unavailable when self-service changes are unavailable), with no redirect.

The billing backend can also apply an eligible existing-subscriber upgrade or schedule a downgrade. The current v1 response reduces a successful backend result without a redirect to { "object": "billing_checkout_session", "url": null, "portal": false }; it does not expose the backend’s applied/scheduled fields. Re-read billing status and use Settings → Billing & Payments → Plan & Billing to review the change. Do not assume every 200 contains a Checkout URL.

Send an Idempotency-Key header and reuse the same key and body when retrying the same request. Create a new key only for a new intentional operation.

Scope required: billing.write (owner-only)

FieldRequiredDescription
planyesTarget plan code: "starter" (Standard), "pro" (Pro), or "scale" (Business)
success_urlnoAccepted and validated as an absolute http(s):// URL; the backend uses its own return URL instead
cancel_urlnoAccepted and validated as an absolute http(s):// URL; the backend uses its own return URL instead

The backend returns users to the workspace’s Settings → Billing & Payments → Plan & Billing page. successUrl and cancelUrl are accepted aliases; if both forms are supplied they must match. Unknown fields, including interval, are rejected.

Terminal window
# Generate once and retain this key for retries of this request.
checkout_key="checkout-$(uuidgen)"
curl -X POST "https://app.benchkey.com/api/v1/billing/checkout" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $checkout_key" \
-d '{ "plan": "scale" }'
// Retain this key if the same request needs to be retried.
const checkoutKey = crypto.randomUUID();
const res = await fetch("https://app.benchkey.com/api/v1/billing/checkout", {
method: "POST",
headers: {
Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa",
"Content-Type": "application/json",
"Idempotency-Key": checkoutKey,
},
body: JSON.stringify({ plan: "scale" }),
});
const result = await res.json();
if (!res.ok) throw new Error(result.error?.message || "Checkout failed");
if (result.url) {
// Redirect the user to result.url.
} else {
// Re-read billing status and review the change in Plan & Billing.
}
{
"object": "billing_checkout_session",
"url": "https://checkout.stripe.com/c/pay/cs_live_example",
"portal": false
}
FieldTypeDescription
urlstring|nullThe Checkout URL for a new session; null when the backend returned no redirect
portalbooleanCompatibility boolean copied from the backend; currently false for these Checkout responses

POST /api/v1/billing/portal

Returns a Stripe Customer Portal URL for payment methods, invoice history, and cancellation. Plan switching is disabled in the Portal. Omit the request body or send an empty JSON object.

Scope required: billing.write (owner-only)

Terminal window
curl -X POST "https://app.benchkey.com/api/v1/billing/portal" \
-H "Authorization: Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa"
const res = await fetch("https://app.benchkey.com/api/v1/billing/portal", {
method: "POST",
headers: { Authorization: "Bearer bk_live_01J8X4_kvWz9nP3mRqTs7uYeBfGhDjLa" },
});
const { url } = await res.json();
// Redirect the user to `url`
{
"object": "billing_portal_session",
"url": "https://billing.stripe.com/p/session/live_example",
"portal": false
}

The current response has portal: false because the backend returns only url. Use the endpoint and url to identify this as a Portal session; do not use that compatibility flag to infer the destination.


POST /api/v1/billing/location-plan

This write endpoint is retired. Every billable location uses the workspace’s main plan. Manage location counts in Settings → Business → Locations; use Settings → Billing & Payments → Plan & Billing for the workspace plan.

Scope required: billing.write (owner-gated billing operation)

The v1 route still validates a JSON object with location_id (a positive integer no greater than 2,147,483,647; locationId is an alias) and plan ("pro" or "scale"). A valid request reaches the retired internal /api/billing/location-plan route, which returns 410 location_plan_retired without changing anything. The current v1 billing error mapper translates that internal response to 422 billing_unprocessable. Do not retry it as a plan assignment.

Public v1 response (422); request_id varies:

{
"error": {
"type": "unprocessable_error",
"code": "billing_unprocessable",
"message": "Per-location plan levels have been retired \u2014 every location rides your main plan. Change your plan from Settings → Plan & Billing; locations are billed automatically as you add them.",
"request_id": "req_example"
}
}

HTTP statusCodeMeaning
400missing_fieldCheckout plan is absent or empty
400invalid_planplan is not one of the accepted values
400invalid_urlsuccess_url or cancel_url is not a valid http(s):// URL
400invalid_location_idlocation_id is missing or is not a positive integer at most 2,147,483,647
400invalid_body / unknown_field / alias_conflictInvalid JSON body shape, unsupported field, or conflicting aliases
400billing_invalid_requestThe billing backend rejected the request
402upgrade_requiredAPI access requires Business (scale)
402billing_subscription_requiredAn active subscription is required for this operation
403billing_forbiddenThe underlying billing operation is not permitted for this tenant
403insufficient_scopeAPI key lacks billing.read or billing.write
404not_foundNo billing customer found for this tenant
409plan_change_unavailableSelf-service plan changes are unavailable; no Portal redirect is created
409starter_multi_locationDeactivate extra billable locations before choosing Standard
409plan_stores_over_limitPro covers one store: deactivate the extra stores or choose Business
409billing_conflictAnother backend billing conflict, including a same-plan request or billing identity/country issue
422billing_unprocessableThe retired location-plan write (internal 410) or another unprocessable billing request
503billing_projection_authority_unavailableBilling status or a public-safe usage/location projection could not be read
503service_unavailableBilling is not configured or temporarily unavailable

See Errors for the full error envelope format.

Estado del sistema