Curated reference

API Surface by Integration Task

Start here for the endpoints most native checkout integrations need. Use the full OpenAPI reference when you need every schema and response field.

Checkout Sessions and Embedded Checkout

Create checkout sessions from your backend, render the hosted iframe in the browser, and complete buyer-facing payment flows.

POST /api/v1/checkout-sessions/embed-token Server-side

Create a payment-ready proxy checkout session.

X-API-Key Guide
Implementation details

Use this for payment-only provider proxy mode when your application owns cart, shipping, and order summary UI. The response returns a Throttle session id plus hosted and embedded URLs.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
amountintegerYesAmount to charge in minor units, for example 2599 for $25.99.
currencystringYesThree-letter ISO 4217 currency code.
countrystringNoTwo-letter ISO 3166-1 country code. Defaults to US.
Default: US
externalCartIdstringNoYour cart reference. Stored on session metadata for reconciliation.

Responses

200 Session created and embed URLs returned.

Response fields

FieldTypeRequiredDescription
checkoutSessionIdstringYesDurable Throttle session id. Use this as the React embed sessionId.
embedTokenstringYesShort-lived provider embed JWT. The hosted iframe can re-mint it later.
hostedUrlurlYesStandalone hosted checkout URL.
embedUrlurlYesChromeless iframe URL for embedded checkout.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
402 workspace_not_live A production-environment credential was used but the workspace has not completed the go-live flow (lifecycleStage is still sandbox). Use a non-production environment key for sandbox testing, or complete the go-live action in the dashboard to unlock production paths.
402 workspace_not_entitled_for_live A production-environment credential was used but the workspace does not have an active paying subscription (and is not a partner). Production payments are paused. Subscribe (or restore your subscription) from the workspace billing page to accept production payments. Non-production environment keys remain fully functional.
402 trial_expired The workspace trial period has ended and no payment method is on file. Add a payment method and subscribe from the workspace billing page.
400 allowed_methods_unsupported allowedMethods was sent to the payment-only embed-token endpoint. The payment widget renders the methods configured on your connection and cannot be filtered here. Remove allowedMethods. To restrict methods, configure the payment connection, or use the full hosted checkout where allowedMethods filters the selection.
422 no_payment_connection The merchant does not have an active payment connection. Connect a payment processor before creating card checkout sessions.

SDK coverage

@usethrottle/checkout-sdk/server · Server-side SDK. Pair the returned session id with @usethrottle/checkout-sdk in React.

  • createCheckoutClient().createEmbedToken(input)

Never mint embed tokens from the browser. The React SDK renders the session id returned by this server helper.

Create session
curl -X POST https://api.usethrottle.dev/api/v1/checkout-sessions/embed-token \
  -H "x-api-key: $THROTTLE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "amount": 2599,
    "currency": "USD",
    "country": "US",
    "externalCartId": "cart_123"
  }'

Production notes

  • Never call this endpoint directly from a browser; it requires your secret API key.
  • The session id is the durable handle. The embed token is short-lived and can be refreshed by the hosted checkout page.
POST /api/v1/checkout/sessions Server-side

Create a cart-backed hosted checkout session.

X-API-Key Guide
Implementation details

Use this when Throttle owns or can reference the cart that should become an order. This is the preferred session creation path for native carts, subscriptions, collect flags, and full checkout.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
applicationIduuidYesApplication that owns the checkout session.
cartIduuidNoThrottle native cart id. Required unless externalCartId is provided.
externalCartIdstringNoMerchant/provider cart reference. Required unless cartId is provided.
returnUrlurlYesBuyer return URL after hosted checkout success.
cancelUrlurlYesBuyer return URL when checkout is cancelled.
customerEmailstringNoBuyer email used for checkout and recurring setup when customer is not supplied.
customerobjectNoOptional prefill object with customerId, externalCustomerId, email, names, phone, shippingAddress, billingAddress, and metadata.
allowedMethodsstring[]NoOptional card/net30 payment method allowlist for this session.
paymentTerms.netNinteger | nullNoOptional cart-level Invoice Terms override, 0-365. Customer netN still wins; null clears the cart override.
recurringobjectNoSubscription intent: plan, amount, trialDays, planName, and create mode auto/manual, plus a cadence — interval (named), or intervalUnit + intervalCount for any cadence from 1 hour to 1 year — and an optional totalPayments (2-60) to make it an installment plan. A custom `schedule` is NOT accepted here (400 invalid_combination); put a scheduled plan on a cart line. Optional and exclusive with cart lines that carry `recurring` (422 recurring_source_conflict). With other lines in the cart, `amount` is required (422 recurring_amount_required).
collectobjectNoControls required checkout fields. Omit collect and Throttle decides the shipping step from the cart: on a checkout with subscriptions, the address step is on when the cart holds at least one shippable one-time line and off otherwise; any other checkout keeps asking. A collect object that leaves shippingAddress out sets it to true. An omitted billingAddress defaults to false, except that it defaults to true when the tax trigger is billing_address, or when no shipping step is shown and the application's tax provider (app_based, an address trigger, the strict fallback policy) needs an address to tax. An explicit value always wins.
discountCodestringNoOptional promotion code to validate and apply synchronously during session creation.
metadataobjectNoUp to 50 keys and 10 KB of merchant-owned JSON metadata.

Responses

201 Checkout session created.

Response fields

FieldTypeRequiredDescription
idstringYesSession id used by hosted checkout and React embed components.
statusopen | completed | expired | cancelledYesSession lifecycle state.
hostedUrl, embedUrlurlNoHosted and iframe-ready checkout URLs when returned by the service.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
402 workspace_not_live A production-environment credential was used but the workspace has not completed the go-live flow (lifecycleStage is still sandbox). Use a non-production environment key for sandbox testing, or complete the go-live action in the dashboard to unlock production paths.
402 workspace_not_entitled_for_live A production-environment credential was used but the workspace does not have an active paying subscription (and is not a partner). Production payments are paused. Subscribe (or restore your subscription) from the workspace billing page to accept production payments. Non-production environment keys remain fully functional.
402 trial_expired The workspace trial period has ended and no payment method is on file. Add a payment method and subscribe from the workspace billing page.
422 discount_invalid discountCode is expired, inactive, over limit, or not valid for this cart. Preview the code first or retry session creation without the code.
422 recurring_source_conflict The session carries a recurring intent and the cart already has lines that carry recurring. Send one source: drop the session recurring intent when the cart lines declare the subscriptions.
422 too_many_subscriptions The cart holds more than 10 subscription lines. Split the purchase, or sell seats on one line with quantity.
422 recurring_not_supported_for_cart A cart with subscription lines was sent with a deposit/balance paymentSchedule. Subscription lines are paid in full; drop the schedule.
422 recurring_amount_required A session recurring intent without amount, on a cart that holds other lines (or on an external-provider cart). Also answered by the public session read, the embed token and completion when a line was added after the session was created; nothing is charged. Send recurring.amount, the renewal price.
422 recurring_customer_required The checkout has a subscription and no customer email or identifier. Send customer.email, customer.externalCustomerId, customer.customerId or customerEmail.

SDK coverage

@usethrottle/checkout-sdk/server · Server-side checkout SDK.

  • createCheckoutClient().createSession(input)

@usethrottle/checkout-sdk/server creates the session; @usethrottle/checkout-sdk renders and tracks it in React.

Create cart-backed session
curl -X POST https://api.usethrottle.dev/api/v1/checkout/sessions \
  -H "x-api-key: $THROTTLE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "applicationId": "00000000-0000-0000-0000-000000000000",
    "cartId": "11111111-1111-1111-1111-111111111111",
    "returnUrl": "https://shop.example.com/thank-you",
    "cancelUrl": "https://shop.example.com/cart",
    "allowedMethods": ["card", "net30"],
    "paymentTerms": { "netN": 45 },
    "collect": { "shippingAddress": true, "billingAddress": true }
  }'
GET /api/v1/checkout-sessions/{id}/embed-token Browser-safe

Refresh the iframe embed token for an existing session.

Session id Guide
Implementation details

The hosted checkout app calls this without a merchant API key so buyers can load or reload a checkout after the original embed JWT expires.

Path parameters

FieldTypeRequiredDescription
idstringYesOpen checkout session id.

Responses

200 Fresh embed token and payment processor context.

Response fields

FieldTypeRequiredDescription
embedTokenstringYesFresh provider embed JWT.
amount, currencyinteger, stringNoAmount and currency resolved from session metadata, cart totals, or the latest calculation snapshot.
processorCheckoutSessionIdstringNoProcessor-side checkout session id.
environment'sandbox' | 'production'NoWhich processor environment the token was signed for, resolved from the session. Mount the payment widget against this value — a single checkout front end serves both sandbox and production sessions, so it cannot be a build-time constant.

Common errors

Status Code Cause Fix
404 not_found No checkout session exists for the supplied id. Create a new session and update the iframe src.
410 session_expired The checkout session expired. Return the buyer to your cart and create a new checkout session.
422 session_not_open The session is completed, cancelled, expired, or otherwise not open. Do not re-use completed sessions. Start a new checkout.

Production notes

  • Most workspaces do not call this directly; the hosted checkout and React SDK paths handle refresh during render.
GET /api/v1/checkout-sessions/{id}/payment-methods Browser-safe

List eligible payment methods for a checkout session.

Session id Guide
Implementation details

The iframe uses this to decide which payment method tiles to show. The server checks connector eligibility and then applies the session allowedMethods filter.

Path parameters

FieldTypeRequiredDescription
idstringYesOpen checkout session id.

Responses

200 Eligible payment method list.

Response fields

FieldTypeRequiredDescription
methods[].methodcard | net30 | stringYesPayment method key used by the completion payload.
methods[].displayNamestringYesBuyer-facing method label.
methods[].connectorIdstringNoConnector that supplied the method.
unavailableReasonobject | undefinedNoOnly present when methods is empty AND a provider is connected but currently renders nothing (e.g. not configured for this environment yet). Contains { code, message }; show message to the buyer instead of a generic empty state.

Common errors

Status Code Cause Fix
404 session_not_found The session id is unknown. Create a new session and re-render checkout.
POST /api/v1/checkout-sessions/{id}/complete Browser-safe

Complete a buyer-facing checkout session.

Session id Guide
Implementation details

Called by the hosted iframe after the buyer submits payment. The session id scopes the operation; your backend should listen for webhooks rather than calling this directly.

Headers

FieldTypeRequiredDescription
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
idstringYesOpen checkout session id.

Request body

FieldTypeRequiredDescription
paymentMethodcard | gr4vy | net30YesCompletion path selected by the buyer.
emailstringNoBuyer email. Required for card and net30 modes when not already collected.
shippingAddressobjectNoShipping address object when the session collect flags require it.
billingAddressobjectNoBilling address object when the session collect flags require it.
paymentTokenstringNoCard payment token for the unified card path.
processorTransactionIdstringNoProvider transaction id for proxy mode.
net30AcceptanceobjectNotermsHash, acceptedAt, companyName, and billingEmail for Net 30 checkout.

Responses

200 Checkout completed.

Response fields

FieldTypeRequiredDescription
orderIduuidYesCreated or updated order id.
paymentIduuidYesPayment record created for the completed checkout.
subscriptionIduuid | nullNoSet only when the checkout created exactly one subscription; null otherwise. Absent on a one-off checkout.
subscriptionIdsuuid[]NoEvery subscription the checkout created, in line order. Present on a checkout with subscriptions ([] if creation failed); absent on a one-off checkout. Key order can differ on an idempotent replay.
status'requires_action'NoPresent ONLY when the card needs a 3-D Secure challenge the buyer must answer. The payment is neither completed nor declined, so no orderId or paymentId is returned: put the challenge in front of the buyer, then call this endpoint again with the same body. Only the server-authorized card path (processorCheckoutSessionId, no processorTransactionId) can return this — a completion driven by the payment embed resolves its own challenge before it ever reaches this endpoint.

Common errors

Status Code Cause Fix
404 not_found Session does not exist. Create a fresh checkout session.
422 missing_required_field The session collect flags require email, shipping, billing, or payment-specific fields. Collect the required fields in the iframe or supply them in the completion payload.
402 payment_failed The processor declined or failed the payment. Show the buyer the failure message and let them retry with a different method.
409 checkout_in_progress Another completion for this session is already in flight. Completion is single-flight to prevent a double charge. Do not submit the same session twice concurrently. Wait for the in-flight completion to resolve; retry only after a returned failure.
409 three_ds_unresolved The card was returned for a 3-D Secure challenge too many times on one session without the challenge ever resolving. Each attempt re-runs the authorization, so the number of attempts per session is capped. Start a new checkout session. Do not poll this endpoint in a loop: call it once, show the buyer the challenge returned by status: requires_action, and call it again only after the challenge finishes.
409 subscription_lines_changed The cart's subscription lines (seats included) changed after the session was created. Checked before any money moves. details carries the session's cartId and, when set, its cancelUrl. Create a new checkout session from the current cart. Send the buyer back to details.cancelUrl to review it.
422 recurring_terms_unsupported Net 30 completion of a session whose cart lines carry subscriptions. Subscriptions are paid by card: complete with the embedded card rail.

SDK coverage

@usethrottle/checkout-sdk/server · Server-side checkout SDK for authenticated proxy-mode completion.

  • createCheckoutClient().completeSession(sessionId, input)

Most storefronts let the hosted iframe call the public complete route. Use the SDK when your backend intentionally owns the completion step.

Carts and Items

Create native carts, mutate line items, select shipping, persist taxes, and convert carts into orders.

POST /api/v1/carts Server-side

Create a native Throttle cart.

X-API-Key Guide
Implementation details

Start here when Throttle should own cart state before checkout. Create the cart from your backend, then add items and calculate totals before converting it to an order.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
applicationIduuidYesApplication that owns the cart.
customerIduuidNoOptional Throttle customer id to attach to the cart.
customerEmailstringNoOptional lightweight email capture (no full customer record). Makes an otherwise-anonymous abandoned cart recoverable. A linked customer’s email takes precedence.
currencystringNoThree-letter currency code.
Default: USD
metadataobjectNoMerchant-owned JSON metadata.

Responses

201 Cart created.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.carts.create(input)
  • client.carts.get(id)
  • client.carts.update(id, input)

The SDK uses camelCase input fields such as applicationId and unitPrice, then maps to the REST API.

GET /api/v1/carts/{id} Server-side

Fetch a cart snapshot.

X-API-Key Guide
Implementation details

Read the current cart, including totals, line items, selected shipping, tax lines, and applied discount state.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Responses

200 Cart snapshot returned.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 cart_not_found The cart id is invalid or belongs to another merchant. Confirm the cart id and API key merchant match.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.carts.get(id)

Use after mutations when your backend needs the authoritative cart snapshot.

PATCH /api/v1/carts/{id} Server-side

Update cart-level buyer and metadata fields.

X-API-Key Guide
Implementation details

Patch the customer id, addresses, notes, or metadata on an open native cart.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Request body

FieldTypeRequiredDescription
customerIduuidNoThrottle customer id.
customerEmailstringNoLightweight email capture; pass null to clear.
shippingAddressCartAddressNoCanonical address (required addressLine1, city, countryCode). Non-canonical keys are rejected at write time.
billingAddressCartAddressNoCanonical address (same shape as shippingAddress).
notesstringNoInternal cart notes.
metadataobjectNoMerchant-owned JSON metadata.

Responses

200 Updated cart returned.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.carts.update(id, input)

SDK input names are camelCase: customerId, billingAddress, shippingAddress.

POST /api/v1/carts/{id}/items Server-side

Add a line item to a cart.

X-API-Key Guide
Implementation details

Use this for products, services, tickets, donations, custom items, and subscription line items before checkout.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Request body

FieldTypeRequiredDescription
typeproduct | subscription | service | ticket | donation | customNoLine item type.
Default: product
referenceIdstringNoYour SKU, plan id, ticket id, or other external item reference.
namestringYesBuyer-facing item name.
descriptionstringNoOptional item description.
unitPriceintegerYesUnit price in minor units (cents).
quantityintegerNoQuantity, minimum 1.
Default: 1
taxAmountintegerNoOptional item tax amount in minor units.
discountAmountintegerNoOptional item discount amount in minor units.
imageUrlstringNoOptional product image. Accepts an absolute http(s) URL or a relative path (e.g. "/images/x.png"). Relative paths are resolved to an absolute URL against the application storefront base URL (set storefrontBaseUrl via PUT /v1/embed-config; falls back to the first allowedOrigin) so the image renders on the hosted checkout.
requiresShippingbooleanNoWhether this item is physically shippable. For external/ad-hoc catalogs with no Throttle product behind the item. Omit to infer from type (everything except service ships); set explicitly to force-include or force-exclude the item from calculated shipping.
metadataobjectNoMerchant-owned item metadata.
recurringobjectNoMakes the line a subscription: { plan (1-255), planName?, interval | intervalUnit + intervalCount (1 hour to 1 year), totalPayments? (2-60), trialDays? (0-365, not with totalPayments), or schedule? }. `schedule` is 2-60 rows of { amount } (row 0, the payment due today) then { amount, intervalUnit: hour|day|week|month, intervalCount } (hour 1-8760, day 1-365, week 1-52, month 1-12): payments that differ in amount and gap. unitPrice must equal schedule[0].amount (400 schedule_first_payment_mismatch); schedule excludes interval, intervalUnit, intervalCount, totalPayments, trialDays and amount (400 invalid_combination); a bad row is 400 invalid_schedule naming the row. A read-back recurring can be re-sent: derived fields are accepted when they agree with the schedule and read-only scheduleUnitTotal must equal the sum of the amounts. A discount code on a cart with an installment line is spread across the plan's payments; a line discountAmount reduces payment 1 only. type is forced to subscription; the line never ships (requiresShipping: true is rejected). A trial line is stored at unitPrice 0 with your price on recurring.amount, and cannot carry taxAmount or discountAmount. At most 10 per cart; terms cannot change later. planName, totalPayments and trialDays also take null, so a line's recurring can be sent back as it reads (without the read-only amount and count).

Responses

201 Line item added.

Response fields

FieldTypeRequiredDescription
iduuidYesLine item id.
namestringYesItem name.
unitPrice, quantity, totalintegerNoPricing fields in minor units.
metadataobjectNoItem metadata.
recurringobject | nullNoStored terms; amount is the renewal price on a trial line.
subscriptionIduuid | nullNoThe subscription this line created. Null until checkout completes.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 too_many_subscriptions The cart already holds 10 subscription lines. Split the purchase, or sell seats on one line with quantity.
422 recurring_not_supported_for_cart The cart is managed by an external cart provider. Use a native Throttle cart for subscription lines.
400 invalid_interval The cadence is outside 1 hour to 1 year, or intervalUnit/intervalCount is incomplete. Send a named interval, or intervalUnit + intervalCount within bounds.
400 invalid_combination trialDays and totalPayments were sent together, or schedule was sent with a cadence, totalPayments, trialDays or amount. An installment plan cannot have a free trial; send one or the other. A schedule defines its own payments and gaps.
400 invalid_schedule recurring.schedule is not 2-60 rows, row 0 carries a gap, a later row has a missing or out-of-range gap, or an amount is not a positive integer. The message names the row, e.g. schedule[2].intervalCount. Fix the named row. Gaps: hour 1-8760, day 1-365, week 1-52, month 1-12.
400 schedule_first_payment_mismatch The line's unitPrice differs from schedule[0].amount. Send unitPrice equal to the first schedule amount.
400 interval_conflict interval names a different cadence than intervalUnit + intervalCount. Send one of them, or make them agree (monthly = month + 1).
400 invalid_total_payments totalPayments is not a whole number from 2 to 60. Send 2 to 60, or omit it for an open-ended subscription.
400 invalid_plan recurring.plan is blank or longer than 255 characters. Send your plan reference, 1 to 255 characters.
400 invalid_trial_days trialDays is not a whole number from 0 to 365. Send 0 to 365, or omit it for no trial.
400 invalid_line_item A free-trial line carries taxAmount or discountAmount. A trial line is charged 0 today; omit tax and discount on it.
404 cart_not_found The cart id is invalid or belongs to another merchant. Read the cart before mutating it and confirm you are using the correct merchant API key.
400 image_url_unresolvable imageUrl is a relative path but the application has no storefront base URL to resolve it against. Send an absolute http(s) imageUrl, or set storefrontBaseUrl via PUT /v1/embed-config (the first allowedOrigin is used as a fallback).

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.items.add(cartId, input)
  • client.items.update(cartId, itemId, input)
  • client.items.remove(cartId, itemId)

SDK inputs use camelCase: referenceId, unitPrice, taxAmount, discountAmount, imageUrl, requiresShipping.

PATCH /api/v1/carts/{cartId}/items/{id} Server-side

Update a line item.

X-API-Key Guide
Implementation details

Patch quantity, price, tax, discount, or metadata for an existing cart line item.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
cartIduuidYesCart id.
iduuidYesLine item id.

Request body

FieldTypeRequiredDescription
quantityintegerNoQuantity, minimum 1.
unitPriceintegerNoUnit price in minor units.
taxAmountintegerNoTax amount in minor units.
discountAmountintegerNoDiscount amount in minor units.
metadataobjectNoReplacement item metadata.

Responses

200 Updated line item returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 recurring_line_immutable The PATCH changes a subscription line's unitPrice, taxAmount or discountAmount. Change quantity or metadata only; remove the line and add it again to change its terms.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.items.update(cartId, itemId, input)

SDK input names are camelCase, matching the wire format.

DELETE /api/v1/carts/{cartId}/items/{id} Server-side

Remove a line item.

X-API-Key Guide
Implementation details

Deletes a line item from an open native cart.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
cartIduuidYesCart id.
iduuidYesLine item id.

Responses

204 Line item removed. No JSON body is returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.items.remove(cartId, itemId)

Read the cart after removal if your backend needs recalculated totals.

POST /api/v1/carts/{id}/shipping Server-side

Select a shipping method for a cart.

X-API-Key Guide
Implementation details

Persist the buyer-selected shipping option before final checkout. Use the id returned from shipping/tax calculation when possible.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Request body

FieldTypeRequiredDescription
methodIdstringYesShipping method id.
displayNamestringYesBuyer-facing shipping label.
rateAmountintegerYesShipping rate in minor units.
currencystringNoThree-letter currency code.
carrierstringNoOptional carrier name.
serviceCodestringNoOptional carrier service code.
estimatedDeliveryDaysintegerNoOptional delivery estimate in days.

Responses

200 Cart returned with selectedShipping populated.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.shipping.select(cartId, input)
  • client.shipping.clear(cartId)

Call clear when the buyer changes address and the previous method is no longer valid.

DELETE /api/v1/carts/{id}/shipping Server-side

Clear selected shipping from a cart.

X-API-Key Guide
Implementation details

Use when the buyer changes address, shipping is no longer required, or the selected method is no longer available.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Responses

200 Cart returned with selectedShipping cleared.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.shipping.clear(cartId)

Recalculate totals after clearing shipping if payment amount depends on shipping.

PUT /api/v1/carts/{id}/tax-lines Server-side

Replace tax lines on a cart.

X-API-Key Guide
Implementation details

Use this when you bring your own tax calculation and need to persist the final line-level tax breakdown on a native cart.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Request body

FieldTypeRequiredDescription
linesarrayYesComplete replacement list of tax lines.
lines[].lineItemIduuidYesLine item receiving the tax.
lines[].jurisdictionCodestringYesTax jurisdiction code.
lines[].jurisdictionNamestringNoHuman-readable jurisdiction name.
lines[].taxTypesales | vat | gst | pst | hst | service | excise | otherYesTax category.
lines[].ratenumberYesDecimal rate between 0 and 1.
lines[].amountintegerYesTax amount in minor units.
lines[].currencystringNoThree-letter currency code.

Responses

200 Cart returned with replacement taxLines.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.taxLines.set(cartId, lines)
  • client.taxLines.clear(cartId)

This is a full replacement operation, not a patch.

DELETE /api/v1/carts/{id}/tax-lines Server-side

Clear all tax lines from a cart.

X-API-Key Guide
Implementation details

Remove persisted tax lines when the cart address changes, the tax mode changes, or you need to recompute taxes from scratch.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Responses

200 Cart returned with taxLines cleared.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.taxLines.clear(cartId)

This removes every tax line on the cart.

POST /api/v1/carts/{id}/apply-discount Server-side

Apply a promotion code to a cart.

X-API-Key Guide
Implementation details

Validates a code and writes the active applied discount snapshot onto the cart. A cart can have one active code; applying a new valid code replaces the old one.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Request body

FieldTypeRequiredDescription
codestringYesPromotion code to apply.

Responses

200 Cart returned with appliedDiscount populated.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 discount_invalid The code is inactive, expired, over usage limits, or not valid for the cart/customer. Keep the existing cart state and show the buyer the returned message.

SDK coverage

@usethrottle/discounts or @usethrottle/cart · Server-side Node clients.

  • discounts.applyToCart(cartId, code)
  • cartClient.discounts.apply(cartId, code)

Use @usethrottle/discounts when discounts are your main workflow; use @usethrottle/cart inside cart orchestration.

DELETE /api/v1/carts/{id}/discount Server-side

Remove the active promotion code from a cart.

X-API-Key Guide
Implementation details

Clears the applied discount snapshot and returns the cart without an active promotion code.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Responses

200 Cart returned with appliedDiscount cleared.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle cart identifier used by all cart mutation endpoints.
statusopen | checkout | converted | abandonedYesCurrent cart lifecycle state. Only open carts should be mutated.
currencystringYesISO 4217 currency used for all cart totals.
subtotal, taxTotal, discountTotal, shippingTotal, totalintegerYesCart totals in minor units.
lineItemsarrayNoLine items included when reading a cart or after item mutations.
appliedDiscountobject | nullNoSnapshot of the active promotion code, if one is applied.
selectedShippingobject | nullNoSelected shipping method and rate, if one has been selected.
taxLinesarrayNoTax line breakdown persisted on the cart.
metadataobjectNoMerchant-owned JSON metadata returned unchanged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/discounts or @usethrottle/cart · Server-side Node clients.

  • discounts.removeFromCart(cartId)
  • cartClient.discounts.remove(cartId)

Recalculate totals after removal if the buyer remains in checkout.

POST /api/v1/carts/{id}/checkout Server-side

Convert a cart into an order.

X-API-Key Guide
Implementation details

Call after items, discount, shipping, and final tax calculation are ready. This creates the order that payment and fulfillment workflows use.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Request body

FieldTypeRequiredDescription
paymentMethodstringNoOptional selected payment method such as card or net30.
metadataobjectNoOptional checkout metadata.

Responses

201 Order created from cart.

Response fields

FieldTypeRequiredDescription
iduuidYesOrder id.
orderNumberstringNoMerchant-visible order number.
statusdraftNoInitial order status.
subtotal, totalintegerNoOrder totals in minor units.
cartIduuidNoSource cart id.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
400 cart_not_ready The cart is missing required checkout state or cannot be converted. Confirm items exist and final totals have been calculated before checkout.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.carts.checkout(cartId, input)

Run shippingTax.calculateCart with kind checkout_final before checkout when Throttle calculates totals.

GET /api/v1/carts/{id}/events Server-side

Read immutable cart events.

X-API-Key Guide
Implementation details

Use cart events for polling/debugging cart mutations. For real-time delivery, subscribe to outbound webhooks.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesCart id.

Query parameters

FieldTypeRequiredDescription
sinceSequenceintegerNoReturn events after this sequence.
limitintegerNoPage size, 1 to 200.

Responses

200 Ordered cart event list.

Response fields

FieldTypeRequiredDescription
iduuidYesEvent id.
eventTypestringYesEvent name such as cart.item_added.
sequenceintegerYesMonotonic per-cart sequence.
payloadobjectNoEvent payload.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.events.list(cartId, { sinceSequence, limit })

Store the last sequence you processed if you poll events.

GET /api/v1/abandoned-carts Server-side

List abandoned carts (most-recently abandoned first).

X-API-Key
Implementation details

Carts that the sweep flagged abandoned, newest first. Cursor-paginated. A cart becomes abandoned after the per-app idle threshold (see embed-config `cartAbandonmentThresholdMinutes`) or once it passes its expiry.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
limitintegerNoPage size, 1 to 100 (default 25).
cursorstringNoOpaque cursor from the previous page.

Responses

200 Cursor-paginated abandoned carts.

Response fields

FieldTypeRequiredDescription
iduuidYesCart id.
customerobject | nullNoBuyer identity ({ id, email, firstName }) or null for an anonymous cart.
totalintegerNoCart total in minor units (cents).
currencystringNoISO-4217 currency code.
itemCountintegerNoNumber of line items.
subscriptionLineCountintegerNoLines that become subscriptions at checkout (a line that carries recurring, or the plan line a session recurring intent added).
abandonedAtstring | nullNoISO-8601 timestamp the cart was marked abandoned.
recoveryStatusstringNoWhether a recovery email was sent, or recovery is webhook-only.
Values: emailed, webhook_only
GET /api/v1/abandoned-carts/summary Server-side

Abandoned-cart summary metrics for a trailing window.

X-API-Key
Implementation details

Headline counts for the abandoned-carts dashboard over a trailing window.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
sinceDaysintegerNoTrailing window in days, 1 to 365 (default 30).

Responses

200 Summary metrics.

Response fields

FieldTypeRequiredDescription
sinceDaysintegerNoWindow applied.
abandonedCountintegerNoCarts abandoned in the window.
abandonedValueintegerNoSum of abandoned-cart totals in minor units (cents).
recoveryEmailsSentintegerNoRecovery emails dispatched in the window.
POST /api/v1/shipping-tax/external-snapshots Server-side

Push provider-owned shipping/tax totals.

X-API-Key Guide
Implementation details

Use when your application is in bring-your-own shipping or tax mode and an upstream provider owns the final totals.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
applicationIduuidYesApplication receiving the snapshot.
cartIduuid | nullNoOptional Throttle cart to mirror totals onto.
checkoutSessionIdstring | nullNoOptional checkout session reference.
orderIduuid | nullNoOptional order reference.
currencystringNoThree-letter currency code.
totalsobjectYessubtotal, discountTotal, shippingTotal, taxTotal, and total in minor units.
selectedShippingobject | nullNoProvider-selected shipping snapshot.
taxLinesarrayNoProvider tax line breakdown.
sourcestringNoProvider/source label for audit.

Responses

200 External snapshot accepted and calculation response returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 external_snapshot_not_allowed Neither shipping nor tax mode is configured for bring-your-own totals. Switch at least one axis to byo mode before pushing external totals.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.shippingTax.pushExternalSnapshot(input)

This is for provider-owned totals; native Throttle carts usually use calculateCart instead.

Discounts

Manage percentage and fixed amount promotion codes, preview them without mutation, and apply them to native carts.

POST /api/v1/discounts Server-side

Create a discount.

X-API-Key Guide
Implementation details

Creates a percentage or fixed amount discount code for later preview and cart application.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
codestringNoPromotion code. Leave empty for automatic discounts.
namestringYesHuman-readable discount name.
typepercentage | fixed_amountYesDiscount type.
valueintegerYesWhole percent for percentage discounts, or minor units for fixed amount discounts.
conditionsobjectNoOptional conditions such as minOrder.
usageLimitintegerNoOptional global usage cap.
maxRedemptionsPerCustomerinteger | nullNoPer-customer redemption cap. Use null for unlimited.
Default: 1
startsAt, endsAtdate-timeNoOptional active window.
metadataobjectNoCampaign or attribution metadata.

Responses

201 Discount created.

Response fields

FieldTypeRequiredDescription
iduuidYesDiscount id.
codestringNoPromotion code.
type, valuestring, integerNoDiscount calculation fields.
isActivebooleanNoWhether the code can be used.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/discounts · Server-side Node client.

  • discounts.create(input)
  • discounts.list(filters)

Use the SDK from API routes, server actions, workers, or backend services only.

GET /api/v1/discounts Server-side

List discounts.

X-API-Key Guide
Implementation details

Use for promotion management screens, campaign reconciliation, or validating which codes exist before checkout.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
cursorstringNoCursor for pagination.
limitintegerNoPage size, 1 to 100.
Default: 10
isActivebooleanNoFilter by active state.
codestringNoFilter by promotion code.
typepercentage | fixed_amountNoFilter by type.
qstringNoSearch code, name, type, or metadata.
sortasc | descNoSort by creation time.

Responses

200 Paginated discount list returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/discounts · Server-side Node client.

  • discounts.list(filters)
  • discounts.get(id)

Use cursor pagination for management UIs.

PATCH /api/v1/discounts/{id} Server-side

Update a discount.

X-API-Key Guide
Implementation details

Patch campaign metadata, active windows, usage limits, or the display name for an existing discount.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesDiscount id.

Request body

FieldTypeRequiredDescription
codestringNoPromotion code.
namestringNoHuman-readable discount name.
valueintegerNoUpdated percent or fixed amount value.
conditionsobjectNoReplacement conditions object.
usageLimitintegerNoUpdated global usage cap.
maxRedemptionsPerCustomerinteger | nullNoUpdated per-customer cap.
startsAt, endsAtdate-timeNoUpdated active window.
metadataobjectNoReplacement metadata.

Responses

200 Updated discount returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/discounts · Server-side Node client.

  • discounts.update(id, input)

Existing cart snapshots keep the discount values captured when the code was applied.

DELETE /api/v1/discounts/{id} Server-side

Deactivate or delete a discount.

X-API-Key Guide
Implementation details

Removes a discount from future use. Existing orders keep their historical discount snapshot.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesDiscount id.

Responses

204 Discount removed. No JSON body is returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/discounts · Server-side Node client.

  • discounts.delete(id)

Prefer deactivation semantics in your UI copy if historical redemptions matter.

POST /api/v1/discounts/preview Server-side

Preview a code without mutating a cart.

X-API-Key Guide
Implementation details

Call while the buyer types a code in your storefront. Preview returns the computed discount and final total without changing cart or usage counts.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
codestringYesPromotion code to preview.
currencystringYesThree-letter currency code.
subtotalintegerYesCurrent subtotal in minor units.
customerIduuidNoOptional customer id for redemption-limit checks.
lineItemsarrayNoOptional line item context for future item-level conditions.

Responses

200 Preview result.

Response fields

FieldTypeRequiredDescription
validbooleanYesWhether the code can currently be applied.
codestringNoNormalized code.
discountTotalintegerNoDiscount amount in minor units.
totalintegerNoSubtotal minus discount.
messagestringNoBuyer-safe reason when invalid.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/discounts · Server-side Node client.

  • discounts.preview(input)

Preview is safe to call repeatedly because it does not mutate usage counts.

Shipping and Tax

Issue storefront quote tokens, estimate buyer totals, and calculate or persist cart totals before checkout.

GET /api/v1/shipping-tax/config Setup

Read draft and published shipping/tax config.

X-API-Key Guide
Implementation details

Use setup tooling or advanced dashboards to show the active shipping/tax modes and catalog for an application.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
applicationIduuidYesApplication id to read config for.

Responses

200 Draft and published configs returned.

Response fields

FieldTypeRequiredDescription
draftobject | nullNoEditable config draft.
publishedobject | nullNoPublished config used by calculations.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
400 missing_application_id applicationId query parameter is missing. Pass applicationId in the query string.
GET /api/v1/shipping-tax/capabilities Setup

Read the tax capabilities the application is running with.

X-API-Key Guide
Implementation details

Reports whether the application is calculating tax through a connected provider, from the published shipping/tax config and the active provider connection for the request’s environment. Fails closed: with no published config or no active connection every flag is false and taxMode is off. Requires the shipping_tax:read scope.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Responses

200 Capabilities returned.

Response fields

FieldTypeRequiredDescription
taxModestringNoThe published config’s tax mode (for example off or app_based).
avalaraConnectedbooleanNoTrue when an Avalara connection is active for this application and environment.
enabledbooleanNoTrue only when taxMode is app_based and Avalara is connected.
recordDocumentsbooleanNoWhether tax documents are recorded with the provider. Always false when enabled is false.
ecmEnabledbooleanNoWhether exemption-certificate management is on. Always false when enabled is false.
validateAddressesbooleanNoWhether buyer addresses are validated. Always false when enabled is false.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
GET /api/v1/shipping-tax/quote-tokens Setup

List publishable quote tokens for an application.

X-API-Key Guide
Implementation details

Use during setup to audit active quote tokens. Raw token values are not returned after creation.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
applicationIduuidYesApplication id to list tokens for.

Responses

200 Quote token metadata list returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/shipping-tax/quote-tokens Setup

Create a storefront quote token.

X-API-Key Guide
Implementation details

Quote tokens start with qt_ (tokens minted before this change start with pk_ and remain valid indefinitely) and let a storefront request read-only estimates without exposing a secret key.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
applicationIduuidYesApplication the token can quote for.
namestringYesInternal token label, 1 to 80 characters.
expiresAtdate-time | nullNoOptional expiration time.

Responses

200 Token created. rawToken is shown once.

Response fields

FieldTypeRequiredDescription
rawTokenstringYesQuote token, prefixed qt_. Store it safely client-side.
tokenPrefixstringNoPrefix shown later for identification.
expiresAtdate-time | nullNoExpiration time.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 store_not_found The application id does not belong to the authenticated workspace. Use a valid application id for this workspace.
DELETE /api/v1/shipping-tax/quote-tokens/{tokenId} Setup

Revoke a storefront quote token.

X-API-Key Guide
Implementation details

Soft-deletes and deactivates a qt_ (or legacy pk_) token so storefront quote requests using it stop working.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
tokenIduuidYesQuote token id.

Responses

200 Revocation confirmation.

Response fields

FieldTypeRequiredDescription
iduuidNoRevoked token id.
revokedbooleanNoAlways true on success.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/shipping-tax/quotes Browser-safe

Request a storefront shipping/tax estimate.

Quote token, or X-API-Key with shipping_quotes:write Guide
Implementation details

Use from the browser or your backend for read-only estimates. Accepts either a quoteToken (qt_, or legacy pk_) validated against application id, expiry, and allowed origin, or an X-API-Key (sk_/pk_) carrying shipping_quotes:write — in which case applicationId and environmentId must match the key.

Headers

FieldTypeRequiredDescription
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
applicationIduuidYesApplication being quoted.
quoteTokenstringNoStorefront quote token (qt_, or legacy pk_) for this application. Optional when authenticating instead with an X-API-Key carrying shipping_quotes:write.
currencystringNoThree-letter currency code. Defaults to application currency or USD.
itemsarrayYesAt least one item to quote.
items[].idstringYesSKU or item id.
items[].subtotalAmountintegerYesLine subtotal in minor units.
items[].quantityintegerNoQuantity, minimum 1.
Default: 1
items[].requiresShippingbooleanNoWhether the item needs shipping rates.
items[].taxCategorystringNoTax category used by configured rules.
discountsTotalintegerNoDiscount total in minor units. Calculated tax is charged on the price after it: the part not already carried as a line discount is spread across every line in proportion, lowering each line's taxable amount. Rules that tax before discounts still tax the full price.
selectedShippingMethodIdstring | nullNoMethod id selected by the buyer.
addresses.shippingobject | nullNoShipping address fields such as countryCode, stateProvince, postalCode.
addresses.billingobject | nullNoBilling address for tax rules that use billing address.

Responses

200 Estimated shipping/tax calculation.

Response fields

FieldTypeRequiredDescription
kindquoteYesCalculation kind.
statusestimated | final | failedYesCalculation status.
shipping.methodsarrayNoAvailable shipping or pickup methods.
tax.taxTotalintegerNoTax total in minor units.
totals.totalintegerNoEstimated final total in minor units.
prompts, warnings, errorsarrayNoMissing info and calculation feedback.

Common errors

Status Code Cause Fix
401 invalid_quote_token No quoteToken and no scoped X-API-Key were sent, or the token is wrong, expired, for another application, or the request origin is not allowed. Create a new token and add your storefront origin to the merchant allowlist, or authenticate with a key carrying shipping_quotes:write.
403 application_mismatch Authenticated with an X-API-Key, but the applicationId in the body does not match the key. Send the applicationId the key was minted for.
403 environment_mismatch Authenticated with an X-API-Key, but the environmentId in the body does not match the key. Send the key's own environmentId.
404 store_not_found The application id is unknown. Use the application id tied to the quote token or key.

SDK coverage

@usethrottle/cart · Browser-safe or backend quote helper.

  • new StorefrontQuoteClient(opts).quote(input)

StorefrontQuoteClient uses a quote token (qt_, or legacy pk_), not your secret API key.

POST /api/v1/shipping-tax/carts/{cartId}/calculate Server-side

Calculate shipping/tax for a native cart.

X-API-Key Guide
Implementation details

Use this from your backend when a Throttle cart exists. Persist checkout_final totals before cart checkout so payment receives locked totals. Pass shippingAddress/billingAddress inline to set the destination and calculate in one request — they are persisted to the cart before calculation, so a separate PATCH /carts/{id} is not required. Unknown fields are rejected with a 400 validation_error.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
cartIduuidYesCart id.

Request body

FieldTypeRequiredDescription
kindcart_estimate | checkout_finalNoEstimate during browsing or final lock before payment.
Default: cart_estimate
selectedShippingMethodIdstring | nullNoBuyer-selected method id to include in final totals.
persistbooleanNoPersist calculation snapshot and mirror selected shipping/tax lines onto the cart when supported.
shippingAddressobject | nullNoOptional inline shipping destination (countryCode, stateProvince, postalCode, city, addressLine1/2). Persisted to the cart before calculation, so you can set the address and get rates in one call instead of a separate PATCH /carts/{id}.
billingAddressobject | nullNoOptional inline billing address; persisted to the cart before calculation.
taxDatestring (YYYY-MM-DD)NoTax the sale at the rate in force on this date instead of today — a back-dated invoice, or a correction re-issued after a rate change. Omit for an ordinary sale. Set the same value on order metadata (metadata.taxDate) so the filed document is taxed as of the date the buyer was quoted. Secret-key only: the publishable-token quote endpoint does not accept it.

Responses

200 Cart calculation response.

Response fields

FieldTypeRequiredDescription
snapshotIduuidNoPersisted snapshot id when a snapshot is stored.
shipping.methodsarrayNoAvailable shipping methods.
shipping.selectedMethodobject | nullNoSelected method if supplied.
tax.linesarrayNoTax line breakdown.
totalsobjectNosubtotal, discountTotal, shippingTotal, taxTotal, and total.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cart · Server-side Node client.

  • client.shippingTax.calculateCart(cartId, input)

Use kind checkout_final after the buyer commits to shipping and payment.

Orders and Payments

Read orders and payments from your backend, issue refunds, retrieve Net 30 invoice PDFs, and resend invoice emails.

PATCH /api/v1/orders/{id}/line-items Server-side

Edit an order’s line items (add / update / remove).

X-API-Key
Implementation details

Add, update (quantity/unitPrice), or remove line items and recompute the order total. On a captured order the total delta is settled automatically: an increase charges the customer’s stored card for the difference; a decrease refunds it (capped at the captured amount). Draft/pending orders edit freely with no money movement. Terminal orders (cancelled/refunded/voided) reject.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Request body

FieldTypeRequiredDescription
addarrayNoNew line items: [{ name, unitPrice, quantity, referenceId? }].
updatearrayNoEdits: [{ lineItemId, quantity?, unitPrice? }].
removearrayNoLine item ids to remove.

Responses

200 The updated order with items + an adjustment { delta, previousTotal, newTotal, chargedPaymentId?, refundedPaymentId? }.
402 payment_failed — the delta charge on the stored card was declined.
409 order_not_editable — the order is in a terminal state.
GET /api/v1/orders Server-side

List orders.

X-API-Key
Implementation details

Use for order history, reconciliation, and merchant-side operations dashboards.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
cursorstringNoCursor for pagination.
limitintegerNoPage size, 1 to 100.
Default: 20
statusdraft | pending | processing | partially_fulfilled | fulfilled | cancelled | closedNoOptional status filter.
paymentStatuspending | authorized | captured | partially_paid | partially_refunded | refunded | failed | voidedNoOptional payment status filter.
qstringNoSearch order id, order number, status, source, discount code, or metadata.
customerIduuidNoOptional customer filter.
subscriptionIduuidNoOrders linked to this subscription: through a line, the order’s subscriptionId, or the legacy metadata pointer.
applicationIduuidNoOptional application filter.

Responses

200 Paginated order list.

Response fields

FieldTypeRequiredDescription
data[]arrayNoOrder records.
meta.pagination.cursorstring | nullNoCursor for the next page.
meta.pagination.hasMorebooleanNoWhether another page exists.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/api-client · Server-side generated REST client.

  • Generated client method for GET /api/v1/orders

For native cart checkout, most applications use webhooks for durable order updates and list APIs for reconciliation.

GET /api/v1/orders/{id} Server-side

Fetch an order and its line items.

X-API-Key
Implementation details

Use for order detail pages, reconciliation, fulfillment handoff, and support tooling.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Responses

200 Order detail returned.

Response fields

FieldTypeRequiredDescription
iduuidNoOrder id.
statusstringNoOrder lifecycle state.
lineItemsarrayNoOrder line items. Type is one of product | subscription | service | ticket | donation | custom | usage. `usage` is written by the renewal job for metered usage billed on a subscription renewal order; it needs no fulfilment (deliveryMethod: none) and never appears on a request body — carts, quotes, and manual order line-item edits reject it. Each line carries `subscriptionId` (the subscription it created, or null) and `recurring` (its terms, or null).
subscriptionIdsuuid[]NoEvery subscription linked to the order, in line order. subscriptionId is set only when there is exactly one.
subtotal, totalintegerNoOrder totals in minor units.
metadataobjectNoOrder metadata.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 not_found The order id is unknown or belongs to another merchant. Verify the order id and merchant API key.

SDK coverage

@usethrottle/checkout-sdk/server · Server-side checkout SDK.

  • createCheckoutClient().getOrder(id)
  • createCheckoutClient().getOrderWithPayments(id)

Prefer webhooks for state changes; read the order when rendering a confirmation page or reconciling checkout success.

POST /api/v1/orders/{id}/status Server-side

Set an order lifecycle status.

X-API-Key
Implementation details

Moves an order to any status in the lifecycle vocabulary, in either direction. Unlike the automatic transitions the engine applies, this accepts any target — a merchant correcting a mis-shipped order needs to move it back. Side effects already applied (captured payments, sent emails, shipped fulfillments) are never rewound; the status label changes, the history records who changed it and why.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Request body

FieldTypeRequiredDescription
statusstringYesOne of draft, pending, processing, partially_fulfilled, fulfilled, cancelled, closed.
reasonstringNoFree-text note stored on the status-history row. Max 500 characters.

Responses

200 The updated order.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 not_found The order id is unknown or belongs to another merchant. Verify the order id and merchant API key.
GET /api/v1/orders/{id}/status-history Server-side

List every status change on an order.

X-API-Key
Implementation details

Append-only audit trail, oldest first. Each row carries fromStatus, toStatus, actorType (user | system | integration), actorId, reason, and source (manual | state_machine | fulfillment | payment | migration).

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Responses

200 Array of status-history rows, oldest first.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 not_found The order id is unknown or belongs to another merchant. Verify the order id and merchant API key.
POST /api/v1/orders/{id}/payments/record Server-side

Record money that arrived outside Throttle.

X-API-Key
Implementation details

Cash at the counter, a bank transfer to the merchant's own account, a cheque. The payment is created already captured, so the order advances exactly as a processor capture would — paymentStatus rolls up, fulfillment unblocks, receipts send. No processor is contacted and no card is charged. A draft order is placed by the recording, so cash against an order created through the API needs no separate checkout call. These payments are excluded from billable GMV. Requires the payment_records:write scope and the application:payments:record permission (admin or finance).

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder the money is for.

Request body

FieldTypeRequiredDescription
amountintegerYesMinor units, at least 1. Part-payments roll up to partially_paid; paying more than the balance settles it.
currencystringNoDefaults to the order's currency.
methodstringYescash | bank_transfer | cheque | external.
receivedAtstringNoISO 8601 date-time the money actually arrived. Defaults to now.
referencestringNoCheque number, till id, wire reference. Max 120 characters.
notestringNoFree-text note. Max 500 characters.

Responses

200 The created payment, already captured, with processor "manual".

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 invalid_order_state The order is cancelled or closed, so there is no lifecycle left to advance. Reopen the order first with POST /v1/orders/{id}/status, then record the payment.
Record a cheque
curl -X POST https://api.usethrottle.dev/api/v1/orders/ord_abc/payments/record \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "amount": 20000, "method": "cheque", "reference": "0114", "note": "Collected at pickup" }'
POST /api/v1/orders/{id}/comp Server-side

Waive the order's remaining balance.

X-API-Key
Implementation details

A comp waives what the buyer still owes. Money already captured stays captured. Nothing is sent to a payment processor and the buyer is never charged. `compAmount` is set to the outstanding balance; `total` is NOT reduced, because the order is still a sale of that size — the comp is a credit recorded against it, and what the buyer owes is `total - compAmount`, so `paymentStatus` settles at `captured` and `displayStatus` reads "… · Comped". The amount is never accepted from the caller — only the reason is. Anything still claiming the waived balance is settled: open authorizations are voided, and unpaid Net-N invoices are voided and their buyers emailed. Requires the order_comps:write scope and the application:orders:comp permission (admin or finance).

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Request body

FieldTypeRequiredDescription
reasonstringYesWhy the balance is being waived. 3–500 characters. Stored on the status history.

Responses

200 The updated order, plus `paymentActions`: one row per void attempted, each { paymentId, action: "void", status: "succeeded" | "failed", error? }. A failed void never undoes the comp.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 validation_error The order owes nothing, or is already comped, or is cancelled/refunded. Comp only an order with an outstanding balance.
Comp an order
curl -X POST https://api.usethrottle.dev/api/v1/orders/ord_abc/comp \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Warranty replacement" }'
POST /api/v1/orders/{id}/comp/reverse Server-side

Undo a comp.

X-API-Key
Implementation details

The order owes what it owed again: `compAmount` returns to 0, `total` is restored, and `paymentStatus` falls back out of `captured` on its own. Side effects of the comp are NOT rewound — an authorization voided at the processor cannot be un-voided, and a voided Net-N invoice is not reissued, so collecting the restored balance means taking a fresh payment. A tax return filed for the comp is reversed by retrying it: POST /v1/tax-documents/{id}/retry. Requires the order_comps:write scope and the application:orders:comp permission.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Responses

200 The updated order with compAmount 0 and its total restored.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 validation_error The order has no comp to reverse. Read the order and check compAmount is greater than 0 first.
Reverse a comp
curl -X POST https://api.usethrottle.dev/api/v1/orders/ord_abc/comp/reverse \
  -H "X-API-Key: $THROTTLE_SECRET_KEY"
GET /api/v1/payments Server-side

List payments for the calling merchant.

X-API-Key
Implementation details

Returns all payments scoped to the calling application, cursor-paginated by createdAt DESC. Filter by `method` (e.g. net30, card), `status` (repeat the param for multi-status OR logic), `order_id` (UUID — returns payments for a specific order), `due_from` / `due_to` (YYYY-MM-DD ISO dates that filter on `metadata.dueDate`), `amount_min` / `amount_max` (integer cents, inclusive bounds), `aging` (Net-N aging bucket: `0_30 | 31_60 | 61_90 | 90_plus`), `limit` (page size), and `cursor` (opaque continuation token from the previous response).

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
methodstringNoFilter by payment method (e.g. net30, card, manual, external).
statusstringNoFilter by payment status. Repeat the parameter for multi-status OR logic (e.g. status=authorized&status=captured).
order_iduuidNoReturn payments belonging to a specific order.
due_fromYYYY-MM-DDNoInclusive lower bound on metadata.dueDate (ISO date).
due_toYYYY-MM-DDNoInclusive upper bound on metadata.dueDate (ISO date).
amount_minintegerNoInclusive minimum payment amount in minor currency units (cents).
amount_maxintegerNoInclusive maximum payment amount in minor currency units (cents).
aging0_30 | 31_60 | 61_90 | 90_plusNoFilter to a Net-N aging bucket based on days since the invoice due date.
limitintegerNoPage size, 1 to 100.
Default: 20
cursorstringNoOpaque pagination cursor returned by the previous response.

Responses

200 Payments matching the filters, ordered by createdAt DESC.

Response fields

FieldTypeRequiredDescription
data[]arrayNoPayment records.
data[].processorResponseobject | nullNoDeprecated. The payment processor's raw record, returned as received. Kept for now; it may be removed in a future API version, and MCP tool results already omit it. Read the payment's own fields (status, amount, capturedAmount, cardBrand, cardLastFour, errorCode) instead.
meta.pagination.cursorstring | nullNoCursor for the next page, or null when no further pages exist.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
GET /api/v1/orders/{id}/payments Server-side

List payments for an order.

X-API-Key
Implementation details

Use when you need to show payment state, processor references, refunds, or Net 30 state for a specific order.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Responses

200 Payments attached to the order.

Response fields

FieldTypeRequiredDescription
iduuidNoPayment id.
statusstringNoPayment status.
amount, currencyinteger, stringNoPayment amount and currency.
methodcard | net30 | manual | external | ...NoPayment method.
processorResponseobject | nullNoDeprecated. The payment processor's raw record, returned as received. Kept for now; it may be removed in a future API version, and MCP tool results already omit it. Read the payment's own fields (status, amount, capturedAmount, cardBrand, cardLastFour, errorCode) instead.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/checkout-sdk/server · Server-side checkout SDK.

  • createCheckoutClient().listOrderPayments(orderId)
  • createCheckoutClient().getOrderWithPayments(orderId)

Use getOrderWithPayments when your confirmation route needs order, payment, and transaction state in one helper.

GET /api/v1/payments/{id}/transactions Server-side

List processor transactions for a payment.

X-API-Key
Implementation details

Use when you need capture, authorization, refund, or failure transaction details for reconciliation and support tooling.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesPayment id.

Responses

200 Payment transactions returned.

Response fields

FieldTypeRequiredDescription
iduuidNoTransaction id.
paymentIduuidNoPayment this transaction belongs to.
typeauthorization | capture | refund | void | ...NoProcessor transaction type.
statusstringNoTransaction status.
amount, currencyinteger, stringNoTransaction amount and currency.
processorResponseobject | nullNoDeprecated. The payment processor's raw record for this transaction, returned as received. Kept for now; it may be removed in a future API version, and MCP tool results already omit it.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/checkout-sdk/server · Server-side checkout SDK.

  • createCheckoutClient().listPaymentTransactions(paymentId)

getOrderWithPayments calls this for every payment and returns transactions nested under each payment.

POST /api/v1/payments/{id}/refund Server-side

Refund a captured payment.

X-API-Key
Implementation details

Refund a payment fully or partially. Use webhooks to update downstream order state after the refund result is recorded.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesPayment id.

Request body

FieldTypeRequiredDescription
amountintegerNoPartial refund amount in minor units. Omit for a full refund.

Responses

200 Refund recorded or processor refund initiated.

Response fields

FieldTypeRequiredDescription
iduuidNoPayment id.
statusstringNoUpdated payment/refund status.
refundedAmountintegerNoTotal refunded amount in minor units when returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
422 refund_not_allowed Payment is not captured, already fully refunded, or amount exceeds refundable balance. Read the payment first and only refund captured, refundable amounts.

SDK coverage

@usethrottle/api-client · Server-side generated REST client.

  • Generated client method for POST /api/v1/payments/{id}/refund

Refunds are operations code, not browser checkout code.

GET /api/v1/payments/{id}/invoice-pdf Server-side

Get a signed Net 30 invoice PDF URL.

X-API-Key
Implementation details

Use for buyer portals or operations tools that need to show or download a Net 30 invoice PDF.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesNet 30 payment id.

Responses

200 Signed download information.

Response fields

FieldTypeRequiredDescription
urlurlYesTemporary signed invoice PDF URL.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 not_found No payment with this id belongs to the merchant. Confirm the payment id and that it belongs to your workspace and application.
409 not_invoice The payment is not a Net 30 invoice (e.g. a card or manual payment), so it has no invoice PDF. Only request an invoice PDF for payments whose method is net30.
409 pdf_not_ready The Net 30 invoice exists but its PDF has not been generated yet. Retry after invoice generation completes.
503 storage_unavailable Object storage could not produce a signed URL for the PDF. Retry shortly; if it persists, contact support.
POST /api/v1/payments/{id}/invoice/resend Server-side

Resend the Net 30 invoice email.

X-API-Key
Implementation details

Queues the customer.net30_invoice_issued email again. The email includes a fresh buyer-facing invoice download link.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesNet 30 payment id.

Responses

200 Invoice email queued.

Response fields

FieldTypeRequiredDescription
messageIduuid | stringYesQueued email message id.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
409 not_invoice The payment is not a Net 30 invoice payment. Only call this endpoint for payments where method is net30.
409 no_recipient No customer or Net 30 billing email is available for the invoice. Confirm the order customer or Net 30 acceptance billing email was captured.
409 email_not_configured Transactional email is not enabled for the API service. Configure RESEND_API_KEY, REDIS_URL, and email settings before resending.
GET /api/v1/payments/{id}/invoice/download?token={token} Browser-safe

Download a Net 30 invoice from an email link.

Signed invoice token
Implementation details

Public buyer route used by invoice emails. Throttle validates the signed token, reloads the payment, and redirects to a short-lived S3 signed URL. Tokens are valid for the snapshotted Net-N term, clamped to 1-365 days.

Path parameters

FieldTypeRequiredDescription
iduuidYesNet 30 payment id.

Query parameters

FieldTypeRequiredDescription
tokenjwtYesSigned invoice download token from the email.

Responses

302 Redirects to a short-lived signed PDF URL.

Response fields

FieldTypeRequiredDescription
LocationurlYesTemporary S3 signed URL for the invoice PDF.

Common errors

Status Code Cause Fix
400 missing_token The token query parameter was omitted. Use the full URL generated by the invoice email.
401 invalid_token The token is expired, malformed, or signed with the wrong secret. Resend the invoice email to issue a fresh buyer link.
409 pdf_not_ready The invoice PDF has not been rendered to object storage yet. Retry after the PDF render/backfill job finishes.

Webhooks

Configure signed HTTPS endpoints, test delivery, inspect attempts, and replay delivery payloads.

GET /api/v1/webhook-endpoints Webhook operations

List configured webhook endpoints.

X-API-Key Guide
Implementation details

Returns active and paused outbound webhook endpoints. Deleted endpoints are excluded.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Responses

200 Webhook endpoint list.

Response fields

FieldTypeRequiredDescription
iduuidNoEndpoint id.
urlurlNoDelivery URL.
enabledEventsarrayNoSubscribed event types.
isActivebooleanNoWhether dispatch is active.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/webhook-endpoints Webhook operations

Create a signed outbound webhook endpoint.

X-API-Key Guide
Implementation details

Register the HTTPS URL that receives durable order, payment, cart, subscription, discount, customer, and fulfillment events.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
urlhttps URLYesYour webhook receiver URL. Must use https.
enabledEventsstring[]YesEvent types to deliver. Must be non-empty and unique.

Responses

201 Webhook endpoint created. signingSecret is returned once.

Response fields

FieldTypeRequiredDescription
iduuidYesWebhook endpoint id.
urlurlYesDelivery URL.
enabledEventsarrayYesSubscribed event list.
isActivebooleanYesWhether delivery is active.
signingSecretstringYesHMAC secret. Store it now; it is shown once.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
Create endpoint
curl -X POST https://api.usethrottle.dev/api/v1/webhook-endpoints \
  -H "x-api-key: $THROTTLE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "url": "https://shop.example.com/api/throttle/webhook",
    "enabledEvents": ["order.created", "payment.captured", "payment.failed"]
  }'
PATCH /api/v1/webhook-endpoints/{id} Webhook operations

Update URL, event list, or active state.

X-API-Key Guide
Implementation details

Use this to rotate receivers, pause delivery, resume delivery, or change subscribed events.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesWebhook endpoint id.

Request body

FieldTypeRequiredDescription
urlhttps URLNoNew delivery URL.
enabledEventsstring[]NoReplacement event list.
isActivebooleanNofalse pauses dispatch; true resumes it.

Responses

200 Updated endpoint returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
DELETE /api/v1/webhook-endpoints/{id} Webhook operations

Soft-delete a webhook endpoint.

X-API-Key Guide
Implementation details

Stops future dispatch and removes the endpoint from list responses. Create a new endpoint to subscribe again.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesWebhook endpoint id.

Responses

200 Deleted endpoint confirmation returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
GET /api/v1/webhook-deliveries Webhook operations

Inspect recent webhook delivery attempts.

X-API-Key Guide
Implementation details

Use for debugging and operational audit views. This endpoint returns attempts, not the endpoint configuration itself.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
endpointIduuidNoFilter by webhook endpoint.
orderIduuidNoFilter by related order.
limitintegerNoPage size, 1 to 200.
Default: 50
offsetintegerNoOffset pagination start.
Default: 0

Responses

200 Delivery page.

Response fields

FieldTypeRequiredDescription
itemsarrayNoDelivery attempts.
totalintegerNoTotal matching attempts.
hasMorebooleanNoWhether another page exists.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/webhook-endpoints/{id}/test Webhook operations

Send a synthetic signed test event.

X-API-Key Guide
Implementation details

Use during setup to verify URL reachability, signature validation, and handler routing without waiting for a real checkout event.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesWebhook endpoint id.

Request body

FieldTypeRequiredDescription
eventTypedocumented outbound webhook event typeNoSynthetic event type. Defaults to payment.captured. Must be one the endpoint is subscribed to; an endpoint subscribed to all events accepts any type.

Responses

200 Synthetic delivery enqueued or sent.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
400 validation_error The endpoint has a non-empty enabledEvents list and eventType is not in it. The message names the allowed types and details.allowedEventTypes lists them. Send an eventType the endpoint is subscribed to, or subscribe the endpoint to it first.
POST /api/v1/webhook-endpoints/{id}/rotate-secret Webhook operations

Rotate the endpoint signing secret (reveal-once).

X-API-Key Guide
Implementation details

Generates a new signing secret and returns it once in the response. The previous secret stops verifying immediately, so update your receiver before (or right after) rotating. Use this to recover from a leaked secret.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesWebhook endpoint id.

Responses

200 New signing secret returned (shown once).

Response fields

FieldTypeRequiredDescription
iduuidNoWebhook endpoint id.
signingSecretstringNoThe new whsec_… secret. Store it now; it is not retrievable later.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/webhook-deliveries/{id}/replay Webhook operations

Replay a prior webhook delivery.

X-API-Key Guide
Implementation details

Re-fires the same event payload with a fresh signature. Use after fixing a receiver bug or recovering from downtime.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesDelivery id.

Responses

200 Replay enqueued or dispatched.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

Production notes

  • Your receiver must still be idempotent because replay intentionally delivers an event more than once.

Embed Config

Allow storefront origins and set merchant branding used by hosted and embedded checkout.

GET /api/v1/embed-config Setup

Read allowed origins and checkout branding.

X-API-Key Guide
Implementation details

Use this in setup tooling or dashboards to show which parent origins and brand values are currently configured.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Responses

200 Current embed config.

Response fields

FieldTypeRequiredDescription
allowedOriginsstring[]NoOrigins allowed to embed checkout.
primaryColorstring | nullNoDefault checkout brand color.
logoUrlurl | nullNoDefault checkout logo.
merchantNamestring | nullNoBuyer-facing merchant name.

SDK coverage

@usethrottle/cli · Developer setup CLI.

  • throttle embed-config get

The CLI is the easiest way to configure origins during setup.

PUT /api/v1/embed-config Setup

Set allowed origins and checkout branding.

X-API-Key Guide
Implementation details

Required before embedding checkout in production. The parentOrigin passed to the SDK must match an allowed origin.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
allowedOriginsstring[]NoAllowed parent origins. Max 20, unique.
primaryColorstringNoBrand color forwarded to hosted checkout (hex `#rrggbb`).
logoUrlurlNoLogo URL used by hosted checkout.
merchantNamestringNoBuyer-facing merchant name, 1 to 100 characters.
cartRecoveryUrlTemplatestring | nullNoAbsolute https URL template for abandoned-cart recovery emails. Must contain a `{cartId}` placeholder (e.g. `https://store.com/cart?c={cartId}`). When set, idle carts with a known customer trigger the recovery email; when unset, recovery is webhook-only. Pass null to clear.
cartAbandonmentThresholdMinutesinteger | nullNoMinutes of inactivity before an open/checkout cart is treated as abandoned (fires `cart.abandoned` + any recovery email). Range 15 to 129600 (90 days). Pass null to clear; when unset, the platform default of 1440 (24h) applies.

Responses

200 Updated embed config returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/cli · Developer setup CLI.

  • throttle embed-config set --origins ...

Use exact origins, including scheme and host. Do not include paths.

Customer Accounts Settings

Enable buyer authentication (registration, sessions, storefront accounts) for an application, per environment. Allowed origins for buyer-auth requests come from the same allowedOrigins list configured via Embed Config above, not a separate setting. Token lifetimes are fixed platform-wide and are not merchant-configurable — the same reasoning as password policy: a merchant cannot weaken the accepted read-revocation lag, and Throttle can raise the floor for everyone at once.

GET /api/v1/applications/{applicationId}/auth-settings Setup

Read buyer-auth settings for an application.

X-API-Key or Clerk session Guide
Implementation details

Requires the applications:read scope (API key) or the application:settings dashboard permission (Clerk). Resolves the environment from the caller (X-Throttle-Environment-Id for Clerk, the key’s own environment for an API key).

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
applicationIduuidYesMust match the caller’s own application; a mismatch returns 403 forbidden.

Responses

200 Current buyer-auth settings for this application + environment.

Response fields

FieldTypeRequiredDescription
enabledbooleanNoWhether /v1/storefront/* is reachable for this application/environment. Defaults to false.
PATCH /api/v1/applications/{applicationId}/auth-settings Setup

Update buyer-auth settings for an application.

X-API-Key or Clerk session Guide
Implementation details

Requires the applications:write scope (API key) or the application:settings dashboard permission (Clerk). Merges into the existing settings JSON — send only the fields you want to change.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
applicationIduuidYesMust match the caller’s own application; a mismatch returns 403 forbidden.

Request body

FieldTypeRequiredDescription
enabledbooleanNoTurn buyer authentication on or off for this application + environment.

Responses

200 Updated buyer-auth settings returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

Production notes

  • Allowed origins for /v1/storefront/* are not set here — they come from the application’s existing allowedOrigins (PUT /api/v1/embed-config). An empty list denies every request in a production environment and allows any origin in a non-production environment.
  • Token lifetimes (access token TTL, refresh token TTL, refresh token absolute TTL) are fixed platform-wide and are not settable here; an unrecognized field is rejected with 400.

Subscriptions

Use recurring checkout plus subscription APIs for buyer portals and merchant-side management.

POST /api/v1/subscriptions Server-side

Create a subscription manually.

X-API-Key Guide
Implementation details

Use for manual creation flows after a vaulted payment method exists. Embed-driven create:auto remains the fastest checkout path.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
customerIduuidYesThrottle customer id.
applicationIduuidNoOptional application id.
planReferencestringYesYour plan id.
planNamestringNoBuyer-facing plan name.
intervalweekly | biweekly | monthly | quarterly | yearly | customNoNamed billing interval. Send this, or intervalUnit + intervalCount, or both (they must agree).
intervalUnithour | day | week | monthNoCadence unit for any interval from 1 hour to 1 year. Send with intervalCount.
intervalCountintegerNoCadence count: every N units. Bounds: 8760 hours, 365 days, 52 weeks, or 12 months.
totalPaymentsintegerNoMakes this an installment plan of exactly this many payments (2-60). Cannot be combined with trialEnd or schedule, and cannot be changed after creation.
schedulearrayNoA custom installment schedule: 2-60 rows. Row 0 is { amount } (the payment already collected); each later row is { amount, intervalUnit: hour|day|week|month, intervalCount } giving the gap after the previous payment (hour 1-8760, day 1-365, week 1-52, month 1-12). Amounts are integer minor units per unit. Exclusive with interval, intervalUnit, intervalCount, totalPayments and trialEnd (400 invalid_combination). Fixed at creation (409 installment_plan_locked on any change).
amountintegerYesRecurring amount in minor units. Optional when schedule is sent; if sent it must equal schedule[0].amount (400 schedule_first_payment_mismatch).
currencystringNoThree-letter currency code, normalised to upper case. Must match the application’s configured currency, or omit it to inherit that currency.
Default: USD
currentPeriodStartdate-timeYesCurrent period start (ISO 8601).
currentPeriodEnddate-timeYesCurrent period end (ISO 8601). Must be after currentPeriodStart, and in the future (a small clock-skew grace is allowed) — a past currentPeriodEnd is never renewed by the cron. Optional with schedule: derived as payment 2's due time.
trialEnddate-timeNoOptional trial end. Cannot be before currentPeriodStart.
metadataobjectNoMerchant-owned metadata.
orderIduuidNoThe order that paid for the first period. The order gets this subscriptionId and the subscription's first invoice records the orderId. 404 when the order is not in this application and environment. An order whose lines already link other subscriptions (a checkout that created its own) keeps its links unchanged; the subscription is still created.

Responses

201 Subscription created. Every subscription response also carries paymentSchedule, scheduleTotal and nextPaymentAmount.

Response fields

FieldTypeRequiredDescription
paymentSchedulearray | nullNoOne row per payment: index (0-based), amount (gross, per unit), discount, net, intervalUnit and intervalCount (null on row 0), status (paid | due | upcoming; a waived payment shows as paid), dueAt (an estimate that moves after a late payment, a pause or a waive; null on paid rows). Equal installment plans get a synthesized schedule; null on ongoing subscriptions.
scheduleTotalinteger | nullNoSum of the rows' net amounts, before tax.
nextPaymentAmountinteger | nullNoThe next payment's net amount; null when completed or cancelled.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
400 interval_conflict interval was sent together with intervalUnit/intervalCount that canonicalize to a different cadence. Send only one of them, or make sure they agree.
400 invalid_interval The cadence is malformed or out of bounds (over 1 year, or interval: "custom" with no unit/count). Check the unit and count against the documented bounds.
400 invalid_combination totalPayments was sent together with trialEnd, or schedule with interval, intervalUnit, intervalCount, totalPayments or trialEnd. Installment plans cannot have a trial. Drop one of the two fields.
400 invalid_schedule schedule is not 2-60 rows, row 0 carries a gap, a later row has a missing or out-of-range gap, or an amount is not a positive integer. The message names the row. Fix the named row.
400 schedule_first_payment_mismatch amount was sent and differs from schedule[0].amount. Omit amount, or send schedule[0].amount.
400 invalid_total_payments totalPayments is outside 2-60. Send a whole number from 2 to 60, or omit it for an ongoing subscription.
400 validation_error currentPeriodEnd is at or before currentPeriodStart, currentPeriodEnd is already in the past, or trialEnd is before currentPeriodStart. Send a currentPeriodEnd after currentPeriodStart and in the future, and a trialEnd no earlier than currentPeriodStart.
422 currency_mismatch currency does not match the application’s configured currency. Send the application’s currency, or omit the field to inherit it.

SDK coverage

@usethrottle/subscriptions/server · Server-side subscription SDK.

  • createSubscriptionsClient().create(input)
  • useCreateSubscription()

The server SDK accepts camelCase fields and can resolve externalCustomerId to the Throttle customer row before create.

GET /api/v1/subscriptions Server-side

List subscriptions for a buyer portal or backend job.

X-API-Key Guide
Implementation details

Filter by Throttle customer id, your external customer id, or status. Proxy this through your backend for buyer-facing portals.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
cursorstringNoCursor for pagination.
limitintegerNoPage size, 1 to 100.
Default: 20
customerIduuidNoThrottle customer id filter.
externalCustomerIdstringNoYour customer id. Mutually exclusive with customerId.
statusactive | paused | cancelled | past_due | trialing | unpaid | completedNoOptional status filter.
intervalweekly | biweekly | monthly | quarterly | yearly | customNoOptional billing interval filter. custom matches every non-named cadence.
installmentbooleanNotrue: only installment plans. false: only ongoing subscriptions.
qstringNoSearch subscription id, customer id, plan, status, interval, or metadata.

Responses

200 Paginated subscription list.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().list(filters)
  • useSubscriptions(filters)

Buyer-facing portals should route hooks through createSubscriptionProxyHandler so reads are pinned to the authenticated externalCustomerId.

GET /api/v1/subscriptions/{id} Server-side

Fetch a subscription.

X-API-Key Guide
Implementation details

Use for buyer portal detail pages, support tooling, and access-gating checks.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Responses

200 Subscription detail returned.

Response fields

FieldTypeRequiredDescription
paymentMethodIduuid | nullNoThe saved card this subscription renews on: the card used at its checkout, or the one picked for it since (PATCH /api/v1/subscriptions/{id}/payment-method, or the buyer's PATCH /api/v1/me/subscriptions/{id}/payment-method). Null means it renews on the customer's default card. Renewals, retries and plan-change charges try it first; after a decline they fall back to the default card. Also on the list response, the storefront subscription views and every subscription.* webhook row.
taxCategorystring | nullNoRead-only. The tax category frozen from the checkout line that created the subscription (that line's metadata.taxCategory). Every renewal is quoted and filed with it, and it survives plan changes. Null when the line had none; renewals then use the default category. Also on the list response and on every subscription.* webhook row.
renewalIssueobject | nullNoRead-only; PATCH cannot set it (400), and it is absent from buyer storefront responses. Shape: { kind: "system_error" | "orphan_charge" | "payment_review", reason, count, firstAt, lastAt, message, alertedAt, charge? }. System reasons: tax_quote_failed, internal_error, period_not_advanced, charge_unverified, config_error. Ordinary system faults alert at the third consecutive failure without dunning. config_error means the processor rejected the request without creating a transaction: alert immediately and retry hourly. charge_unverified retains an uncertain charge; re-read its current state before any fallback, never charge another card on uncertainty. At 12 hours it becomes payment_review / charge_unresolved and alerts the merchant. Cancellation of a pending charge also creates review. orphan_charge / record_failed means money was captured but could not be recorded. Pending charges, orphan charges and payment reviews block retry-charge, waive-period and other immediate changes with 409 renewal_needs_attention; only the same pending plan change can be retried before review to reconcile and apply it. A captured but unapplied plan change waits for that retry, then needs staff reconciliation after 12 hours. Scheduled changes and cancellation remain allowed. Public charge fields are { transactionId, amount, currency, periodStart }; frozen requests, idempotency keys and reconciliation instructions remain private. message is fixed copy for reason, never raw error text.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().get(id)
  • useSubscription(id)

The proxy helper verifies the subscription belongs to the authenticated buyer before forwarding mutations.

PATCH /api/v1/subscriptions/{id} Server-side

Change subscription plan or metadata.

X-API-Key Guide
Implementation details

Updates plan reference, display name, interval, amount, or metadata. Changes apply according to the subscription lifecycle rules documented in the guide.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Request body

FieldTypeRequiredDescription
planReferencestringNoUpdated plan id.
planNamestringNoUpdated plan name.
intervalweekly | biweekly | monthly | quarterly | yearly | customNoUpdated interval. Send with intervalUnit + intervalCount, or instead of them.
intervalUnithour | day | week | monthNoUpdated cadence unit for any interval. Send with intervalCount.
intervalCountintegerNoUpdated cadence count.
amountintegerNoUpdated amount in minor units.
metadataobjectNoReplacement metadata.

Responses

200 Updated subscription returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
409 installment_plan_locked The subscription is an installment plan (totalPayments is set); its amount, cadence, quantity and plan are fixed at creation. Cancel the plan and create a new subscription to change the terms.
400 validation_error The body included renewalIssue, which is read-only and written only by the renewal job. Remove renewalIssue from the request body.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().update(id, input)
  • createSubscriptionsClient().changePlan(input)
  • useUpdateSubscription()
  • useChangePlan()

A term-only edit: no proration, no charge, no period reset. Use POST /api/v1/subscriptions/{id}/change-plan (changePlan()) for a real plan change that prorates and charges. Use your own plan catalog as the source of truth for planReference and amount.

POST /api/v1/subscriptions/{id}/pause Server-side

Pause a subscription.

X-API-Key Guide
Implementation details

Moves an active subscription into paused state from your backend proxy.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Responses

200 Paused subscription returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().pause(id)
  • usePauseSubscription()

The proxy helper verifies buyer ownership before forwarding pause requests.

POST /api/v1/subscriptions/{id}/resume Server-side

Resume a paused subscription.

X-API-Key Guide
Implementation details

Moves a paused subscription back into an active billing state.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Responses

200 Resumed subscription returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().resume(id)
  • useResumeSubscription()

The proxy helper verifies buyer ownership before forwarding resume requests.

PATCH /api/v1/subscriptions/{id}/payment-method Server-side

Change the card one subscription renews on.

X-API-Key Guide
Implementation details

Sets the subscription's paymentMethodId to another of its customer's saved cards. Only this subscription moves: the customer's default card and their other subscriptions stay as they are (setting a new default card moves every subscription on the old default). Nothing is charged now; the next renewal or dunning retry charges the new card. Allowed in every state except cancelled and completed, installment plans included. Fires subscription.updated once; the card the subscription already has is a no-op that fires nothing.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Request body

FieldTypeRequiredDescription
paymentMethodIduuidYesOne of the subscription customer's saved cards (GET /api/v1/customers/{id}/payment-methods) in this application and environment: active, in date, and vaulted for renewals. No other field is accepted.

Responses

200 The subscription, with the new paymentMethodId.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
409 payment_method_unusable
409 invalid_state
404 not_found

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().setPaymentMethod(id, paymentMethodId)

Requires subscriptions:write. The proxy helper verifies buyer ownership before forwarding. Buyers signed in to the storefront plane use PATCH /v1/storefront/me/subscriptions/{id}/payment-method instead.

POST /api/v1/subscriptions/{id}/retry-charge Server-side

Retry the failed charge on a past-due subscription.

X-API-Key Guide
Implementation details

Runs the renewal engine now instead of waiting for the next scheduled dunning attempt. A success advances the period and mints the invoice, the order and the buyer email exactly as the cron would; a failure walks the dunning ladder one rung. Only a past-due subscription has a failed charge to retry.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Responses

200 The subscription as it stands after the attempt — active when the card cleared, still past_due when it did not.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
409 conflict
409 renewal_needs_attention The subscription has a pending charge, orphan_charge or payment_review renewalIssue. Resolve the existing charge before requesting another. Wait for pending reconciliation, or contact Throttle support to resolve the retained charge.
422 invalid_state

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK.

  • createSubscriptionsClient().retryCharge(id)

Each attempt is charged separately at the payment provider, so a card the buyer has since fixed goes through.

POST /api/v1/subscriptions/{id}/waive-period Server-side

Forgive the current period on a past-due subscription.

X-API-Key Guide
Implementation details

Writes the period off rather than collecting it: the period advances and the dunning counters clear, leaving the same artifacts as any zero-amount renewal — an order and a captured $0 payment, both marked waived with your reason. No invoice is billed. Any usage reported against the waived period is forgiven along with the base amount and is never billed later. Past-due only: an active subscription already paid, and giving that money back is a refund, not this endpoint.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Request body

FieldTypeRequiredDescription
reasonstringYesWhy the money was not collected, 1–500 characters. Recorded on the waived order.

Responses

200 The subscription, active again.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
409 renewal_needs_attention The subscription has a pending charge, orphan_charge or payment_review renewalIssue. Waiving would leave the existing charge unaccounted for. Wait for pending reconciliation, or contact Throttle support to resolve the retained charge.
422 invalid_state

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK.

  • createSubscriptionsClient().waivePeriod(id, { reason: 'goodwill' })

The waived order reads back as waived, never as a free plan.

POST /api/v1/subscriptions/{id}/cancel Server-side

Cancel a subscription immediately or at period end.

X-API-Key Guide
Implementation details

Use from buyer portals and support tooling. For buyer-facing use, authorize ownership in your backend before calling Throttle.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Request body

FieldTypeRequiredDescription
atPeriodEndbooleanNoIf true, cancels at the end of the current billing period.
Default: false
refund'none' | 'prorated' | 'last_cycle'NoMoney to return with an immediate cancellation. `prorated` refunds the unused part of the current period, capped at what was paid for it; if nothing was paid for the current period the subscription is still cancelled with a refund of 0. `last_cycle` refunds the last paid cycle. Rejected with atPeriodEnd: true, and 409 invalid_state on a subscription already cancelled or completed.
Default: 'none'

Responses

200 Updated subscription returned.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().cancel(id, { atPeriodEnd })
  • useCancelSubscription()

Pause and resume follow the same backend-proxy pattern.

POST /api/v1/subscriptions/eligibility-check Server-side

Check trial eligibility for a vaulted card.

X-API-Key Guide
Implementation details

Pre-flight trial fraud protection by checking whether a payment method fingerprint has already used a trial for this merchant.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
paymentMethodIduuidYesCustomer payment method id owned by the authenticated merchant.

Responses

200 Eligibility result.

Response fields

FieldTypeRequiredDescription
eligiblebooleanNoWhether the payment method can receive a trial.
reasonstringNoReason when blocked.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hook through your backend proxy.

  • createSubscriptionsClient().checkTrialEligibility({ paymentMethodId })
  • useTrialEligibility()

Use after a card is vaulted when your UI needs to decide whether to offer a trial.

GET /api/v1/customers/by-external/{externalId} Server-side

Resolve a customer by your own customer id.

X-API-Key Guide
Implementation details

Use when your app stores only its own user id. Throttle resolves the canonical customer row for subscription lists, direct subscription creates, and payment-method reads.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
externalIdstringYesYour customer or user id previously passed as externalCustomerId.

Responses

200 Customer returned.

Response fields

FieldTypeRequiredDescription
iduuidYesThrottle customer id.
externalIdstring | nullNoYour customer id when one is attached.
emailstring | nullNoCustomer email.
metadataobjectNoCustomer metadata.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 not_found No Throttle customer is associated with that external id. Create a checkout session or customer with customer.externalCustomerId first.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hooks through your backend proxy.

  • createSubscriptionsClient().getCustomerByExternalId(externalId)
  • useCustomerByExternalId(externalId)

createSubscriptionProxyHandler pins this path to the authenticated buyer and ignores ids supplied by the browser.

GET /api/v1/customers/{id}/payment-methods Server-side

List saved payment methods for a customer.

X-API-Key Guide
Implementation details

Use on subscription confirmation pages and buyer portals when you need to show the vaulted card summary used for renewals.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesCustomer id.

Responses

200 Saved payment-method summaries returned.

Response fields

FieldTypeRequiredDescription
iduuidYesPayment method id.
methodTypecard | stringNoPayment method type.
cardBrandstring | nullNoCard brand.
cardLastFourstring | nullNoLast four digits for card methods.
isDefaultbooleanNoWhether this method is the default renewal method.
isActivebooleanNoWhether this method can be used for future renewals. Paused methods remain visible but are not charged.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.

SDK coverage

@usethrottle/subscriptions/server · Server-side SDK plus browser hook through your backend proxy.

  • createSubscriptionsClient().listCustomerPaymentMethods(customerId)
  • useCustomerPaymentMethods(customerId)

When routed through createSubscriptionProxyHandler, payment-method reads are resolved from the authenticated externalCustomerId instead of trusting the browser-supplied customer id.

POST /api/v1/customers/{customerId}/payment-methods/{id}/pause Server-side

Pause a saved payment method.

Clerk JWT or X-API-Key
Implementation details

Keeps the vaulted payment method on file but prevents renewals from charging it until it is resumed.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
customerIduuidYesCustomer id.
iduuidYesPayment method id.

Responses

200 Updated payment method returned with isActive=false.

Response fields

FieldTypeRequiredDescription
iduuidYesPayment method id.
isActivebooleanNoFalse after pause.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/customers/{customerId}/payment-methods/{id}/resume Server-side

Resume a saved payment method.

Clerk JWT or X-API-Key
Implementation details

Allows a previously paused vaulted payment method to be used for future renewals again.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
customerIduuidYesCustomer id.
iduuidYesPayment method id.

Responses

200 Updated payment method returned with isActive=true.

Response fields

FieldTypeRequiredDescription
iduuidYesPayment method id.
isActivebooleanNoTrue after resume.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/subscriptions/{id}/change-plan Server-side

Change a subscription plan immediately or at period end.

X-API-Key Guide
Implementation details

Use effective: "now" to change immediately: the stored card is charged the new amount minus a credit for the unused paid time of the current period, and the billing period resets. Use effective: "period_end" to defer (sets pending_* fields, applies on the next renewal). Returns the updated subscription; an immediate change also returns proration { creditCents, chargedCents, fullAmount }.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Request body

FieldTypeRequiredDescription
planReferencestringYesYour plan id from your plan catalog.
planNamestringNoBuyer-facing plan name.
intervalweekly | biweekly | monthly | quarterly | yearly | customNoBilling interval for the new plan. Send this, or intervalUnit + intervalCount, or both (they must agree).
intervalUnithour | day | week | monthNoCadence unit for the new plan, for any interval. Send with intervalCount.
intervalCountintegerNoCadence count for the new plan.
amountintegerYesRecurring amount in minor units for the new plan.
effective"now" | "period_end"Yes"now" charges the card and applies immediately (upgrade path). "period_end" defers the change to next renewal (downgrade path).

Responses

200 Updated subscription returned.

Response fields

FieldTypeRequiredDescription
iduuidYesSubscription id.
statusstringYesCurrent status.
pendingPlanReferencestring | nullNoSet when effective: "period_end". Contains the deferred plan id. Null for effective: "now".
pendingPlanNamestring | nullNoDeferred plan display name. Null when no change is pending.
pendingIntervalstring | nullNoDeferred billing interval. Null when no change is pending.
pendingIntervalUnitstring | nullNoDeferred cadence unit. Null when no change is pending.
pendingIntervalCountinteger | nullNoDeferred cadence count. Null when no change is pending.
pendingAmountinteger | nullNoDeferred amount in minor units. Null when no change is pending.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
400 invalid_subscription_state Nothing to change: the plan reference, cadence and amount all equal the current terms. (A same-plan scheduled change with a different cadence or amount is allowed.) Only call change-plan when the terms are actually changing.
409 invalid_state effective: "period_end" on a subscription that already has cancelAtPeriodEnd: true. A scheduled change and a scheduled cancellation cannot coexist. Undo the cancellation first with POST /api/v1/subscriptions/{id}/resume, then schedule the change.
422 invalid_state The subscription is cancelled or completed, or it is paused and effective is "now" (an immediate change would charge the card). Resume it first, or use effective: "period_end". A cancelled or completed subscription cannot be changed.
400 interval_conflict interval was sent together with intervalUnit/intervalCount that canonicalize to a different cadence. Send only one of them, or make sure they agree.
409 installment_plan_locked The subscription is an installment plan; its amount, cadence, quantity and plan are fixed at creation. Cancel it and create a new subscription to change the terms.
409 renewal_needs_attention effective: "now" while an orphan charge, payment review or a different pending charge blocks this change. effective: "period_end" is still accepted. Retry only the same pending change before review; otherwise contact Throttle support. A scheduled change remains available.
409 payment_unverified The original charge has no final answer; details.transactionId is its id or null. Retry the same change to reconcile the frozen charge. Do not try another card; at 12 hours it becomes payment_review and needs support.
402 payment_failed The stored card charge was declined, or message is processor_config_error because the processor rejected the request without a transaction. For a decline, let the buyer update their card. For processor_config_error, check connector credentials/configuration using details.reason and details.resolution.
404 not_found No subscription found for the given id. Confirm the subscription id and API key belong to the same application.
Immediate upgrade
curl -X POST https://api.usethrottle.dev/api/v1/subscriptions/sub_abc/change-plan \
  -H "x-api-key: $THROTTLE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "planReference": "pro_yearly",
    "planName": "Pro Yearly",
    "interval": "yearly",
    "amount": 29900,
    "effective": "now"
  }'
Scheduled downgrade
curl -X POST https://api.usethrottle.dev/api/v1/subscriptions/sub_abc/change-plan \
  -H "x-api-key: $THROTTLE_API_KEY" \
  -H "content-type: application/json" \
  -d '{
    "planReference": "starter_monthly",
    "planName": "Starter Monthly",
    "interval": "monthly",
    "amount": 999,
    "effective": "period_end"
  }'

Production notes

  • effective: "now" prorates. The credit base is the value of the current period (cash paid plus credit already applied on its invoice(s), capped at the plan base); the unused share of it is credited against the new amount. The stored card is charged the remainder (never below zero), the billing period resets from now, subscription.plan_changed fires with the proration, and any pending_* fields are cleared. A change fully covered by credit charges nothing and records a $0 paid invoice carrying creditAppliedCents. A past_due subscription is charged in full and returns to active; a trialing subscription is charged nothing and keeps its trial.
  • The same change on the same card within 24 hours of a definitive decline replays that decline. A different card or a new change gets a new key. This does not bypass a pending charge or payment review.
  • Refunds stay cash-only: a prorated cancel never refunds more than the cash paid for the period.
  • effective: "period_end" is the downgrade path. No charge is made. The pending_* fields are written, subscription.plan_change_scheduled fires, and the renewal cron applies the change on the next period end.
  • A pending change can be cancelled with DELETE /api/v1/subscriptions/:id/pending-change.
  • A new change-plan call overwrites any existing pending_* fields.
  • cancelAtPeriodEnd and a pending change never coexist: scheduling a change on a subscription already set to cancel at period end is 409 invalid_state, and cancelling clears any pending change.
POST /api/v1/subscriptions/{id}/change-quantity Server-side

Change the seat quantity (per-seat pricing).

X-API-Key Guide
Implementation details

The subscription amount is the per-seat price; the period charge is amount × quantity. effective: "now" prorates immediately (credits unused time on the old total, charges the net of the new total, resets the period) and returns a proration object; effective: "period_end" defers via pending_quantity, applied by the renewal cron. Emits subscription.updated.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Request body

FieldTypeRequiredDescription
quantityintegerYesNew seat count (≥ 1).
effectivestringYes"now" (prorated, immediate) or "period_end" (deferred).
Values: now, period_end

Responses

200 Updated subscription. Immediate changes include a proration object { creditCents, chargedCents, fullAmount }.
400 quantity_unchanged — the new quantity equals the current one ("Subscription already has N seat(s)"); checked before any charge.
409 invalid_state — effective: "period_end" on a subscription that already has cancelAtPeriodEnd: true.
422 invalid_state — the subscription is cancelled or completed, or it is paused and effective is "now".
402 payment_failed — the prorated seat-increase charge was declined, or message is processor_config_error for a rejected processor request; check connector credentials/configuration.
409 payment_unverified — the charge has no final answer; details.transactionId is its id or null. Retry the same quantity change to reconcile it; review begins at 12 hours.
409 installment_plan_locked — the subscription is an installment plan; its quantity is fixed at creation.
409 renewal_needs_attention — effective: "now" is blocked by an orphan charge, payment review or a different pending charge. effective: "period_end" is still accepted; contact support to resolve the retained charge.
Add seats now (prorated)
curl -X POST https://api.usethrottle.dev/api/v1/subscriptions/sub_abc/change-quantity \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "quantity": 5, "effective": "now" }'
DELETE /api/v1/subscriptions/{id}/pending-change Server-side

Cancel a scheduled plan change.

X-API-Key Guide
Implementation details

Clears the pending_* fields set by a prior change-plan call with effective: "period_end". The subscription continues on its current plan. Emits subscription.updated.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesSubscription id.

Responses

200 Updated subscription returned with all pending_* fields null.

Response fields

FieldTypeRequiredDescription
iduuidYesSubscription id.
pendingPlanReferencenullNoAlways null after clear.
pendingPlanNamenullNoAlways null after clear.
pendingIntervalnullNoAlways null after clear.
pendingAmountnullNoAlways null after clear.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
404 not_found No subscription found for the given id. Confirm the subscription id and API key belong to the same application.
Cancel pending change
curl -X DELETE https://api.usethrottle.dev/api/v1/subscriptions/sub_abc/pending-change \
  -H "x-api-key: $THROTTLE_API_KEY"

Production notes

  • Safe to call even when no pending change exists — the call is a no-op and returns the current subscription.
  • Does not affect cancelAtPeriodEnd. Use POST /api/v1/subscriptions/:id/resume to undo a scheduled cancellation (PATCH does not accept cancelAtPeriodEnd).

Workspace Environments

Manage the workspace-wide environment catalog. Each workspace has one immutable production environment; every custom environment is non-production and routes to sandbox-provider credentials. These routes are selected by workspace id and do not require X-Throttle-Environment-Id.

GET /api/v1/workspaces/{workspaceId}/environments Setup

List workspace environments.

Clerk JWT Guide
Implementation details

Returns active workspace environments by default. Add includeArchived=true for settings, audit, or recovery screens.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.

Query parameters

FieldTypeRequiredDescription
includeArchivedtrue | falseNoWhen true, includes archived custom environments.
Default: false

Responses

200 Workspace environments returned.

Response fields

FieldTypeRequiredDescription
iduuidYesEnvironment id.
slugstringYesEnvironment slug used in API key prefixes.
namestringYesHuman-readable environment name.
kindproduction | non_productionYesProduction is the only production kind.
providerEnvironmentproduction | sandboxYesProvider routing target.
statusactive | archivedYesArchived environments are hidden from normal selectors.
isSystembooleanYesSystem environments cannot be archived.

Common errors

Status Code Cause Fix
403 application_key_workspace_route An API key was used for a workspace-level route. Call this route from the dashboard or another Clerk-authenticated setup flow.
POST /api/v1/workspaces/{workspaceId}/environments Setup

Create a custom non-production environment.

Clerk JWT Guide
Implementation details

Creates a new sandbox-provider environment such as UAT, staging, or QA. Production is created by the system and cannot be created through this endpoint. Existing applications in the workspace receive fresh per-environment settings containers immediately.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.

Request body

FieldTypeRequiredDescription
slugstringYesUnique workspace slug. It is normalized to lowercase kebab-case. It cannot start with a production or live component — production, live, production-1, and live-2 are all rejected, because their key segments would read as live credentials. Slugs that merely begin with those letters, such as productionish or livemode, are fine.
namestringYesDisplay name shown in the dashboard selector and settings.

Responses

201 Custom environment created.

Common errors

Status Code Cause Fix
400 reserved_environment_slug Slug starts with a production or live component — production, live, production-1, live-2. Use a non-production slug such as uat, staging, qa, or preview.
409 environment_slug_taken Another environment in the workspace already uses that slug. Pick a unique slug.
DELETE /api/v1/workspaces/{workspaceId}/environments/{environmentId} Setup

Archive a custom environment.

Clerk JWT Guide
Implementation details

Archives a non-system workspace environment. Historical rows keep their environment id; the environment is removed from normal selectors and new work should use another active environment. Production cannot be archived or deleted.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.
environmentIduuidYesEnvironment id to archive.

Responses

200 Environment archived.

Common errors

Status Code Cause Fix
400 system_environment_locked The requested environment is production or another system environment. Only archive custom non-production environments.
404 environment_not_found Environment id is unknown for this workspace. Refresh the environment list and retry.

Auth and API Keys

API keys are workspace credentials minted from the dashboard. Two types exist: secret keys (sk_) for server-side use — they may hold any granted scope, including the wildcard — and publishable keys (pk_) that are safe to embed in browser/frontend code and may hold only the curated publishable-allowed subset of scopes (currently five, including customer_accounts:write for storefront customer auth). GET /api/v1/scopes is the authoritative, always-current list of which scopes are publishable — see the scopes reference. Both key types carry the workspace environment segment — live for production, the environment slug otherwise (for example sk_uat_ or sk_live_) — that pins the key to one environment. Never expose a secret key in browser code.

GET /api/v1/whoami Setup

Identify the calling credential.

Clerk JWT or X-API-Key
Implementation details

Returns the credential type, its granted scopes, and the workspace, application and environment it is pinned to. Requires authentication but no particular scope, so a minimal key can always read its own grants — useful for tools that adapt their behaviour to what the key may do (the Throttle MCP server registers only the tools the key can execute). Never returns a secret: apiKey.keyPrefix is the same non-sensitive prefix the dashboard shows.

Headers

FieldTypeRequiredDescription
authorization or x-api-keystringYesClerk JWT for dashboard flows or an API key for programmatic callers.

Responses

200 Caller identity.

Response fields

FieldTypeRequiredDescription
typestringNoCredential type: api_key, clerk_jwt, extension_session or pm_client_token.
scopesstring[]NoGranted scopes. Clerk sessions carry ["*"].
workspaceobjectNoid, name and slug of the workspace the credential belongs to.
applicationobject | nullNoid, name and slug of the pinned application; null for workspace-scoped callers.
environmentobjectNoid, slug, kind (production | non_production) and providerEnvironment (production | sandbox) of the pinned environment.
apiKeyobject | nullNoid, name and keyPrefix for API-key callers; null otherwise. Never the raw key.
GET /api/v1/api-keys Setup

List active API keys for the current merchant.

Clerk JWT or X-API-Key
Implementation details

Use in dashboard or setup tooling. Runtime storefront code should not list keys.

Headers

FieldTypeRequiredDescription
authorization or x-api-keystringYesClerk JWT for dashboard flows or secret key for backend setup tooling.

Responses

200 Active key metadata. Raw secret values are never returned.
POST /api/v1/api-keys Setup

Create a secret (sk_) or publishable (pk_) API key.

Clerk JWT Guide
Implementation details

Mint a new key for the selected workspace environment. To mint a browser-safe publishable (pk_) key, include the sentinel string "publishable" as the first entry of the scopes array; otherwise a server-side secret (sk_) key is created. The minted key inherits the request environment selected by X-Throttle-Environment-Id, so the raw value includes the environment segment — live for production, the environment slug otherwise, for example sk_uat_/pk_uat_ or sk_live_/pk_live_. Requested scopes are validated against the @core/scopes registry: unknown scopes, staff-only scopes (the admin:* family), and — for publishable keys — any scope GET /api/v1/scopes does not mark publishable (including the wildcard) are rejected with 400 invalid_scope. The raw key is returned once; store it securely and never ship a secret key to the browser. Only Clerk-authenticated dashboard callers may create keys (an sk_ key cannot mint another key — 403 forbidden).

Headers

FieldTypeRequiredDescription
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
namestringYesKey label.
scopesstring[]YesScopes assigned to the key. Secret keys accept any granted scope or the wildcard "*". To mint a publishable (pk_) key, prepend the sentinel "publishable" to the array; a pk_ key then accepts only the scopes GET /api/v1/scopes reports as publishable (never the wildcard).

Responses

201 Key created.

Response fields

FieldTypeRequiredDescription
rawKeystringYesRaw key, prefixed by type + environment segment — live for production, the environment slug otherwise (for example sk_uat_ or pk_live_). Returned once.
id, name, scopesstring, string, arrayNoKey metadata.

Common errors

Status Code Cause Fix
400 invalid_scope A requested scope is unknown, is staff-only (admin:*), or — for a publishable key — is not one GET /api/v1/scopes marks publishable (or includes the wildcard). Pick scopes from the scopes reference. For a pk_ key, request only scopes flagged publishable in GET /api/v1/scopes.
DELETE /api/v1/api-keys/{id} Setup

Revoke an API key.

Clerk JWT
Implementation details

Use when rotating credentials or removing access for an integration. Revoked keys can no longer call secret-key endpoints.

Path parameters

FieldTypeRequiredDescription
iduuidYesAPI key id.

Responses

204 Key revoked. No JSON body is returned.

Common errors

Status Code Cause Fix
404 not_found The key id is unknown or belongs to another merchant. Refresh the key list and retry with a valid key id.

Workspace Invitations

Create, list, resend, revoke, and accept workspace membership invitations. Invitations are signed 7-day JWTs sent by email; the accept endpoint binds the Clerk user to the workspace.

POST /api/v1/workspaces/{workspaceId}/invites Setup

Create a workspace invitation.

Clerk JWT Guide
Implementation details

Sends an invitation email to the target address. The invitee must accept with a Clerk account whose email matches exactly. Requires workspace:invite permission (or workspace:invite-admin when granting workspace_admin role). Owners and workspace admins can set environmentGrants; application admins default to all_non_production for app-scoped invites.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.

Request body

FieldTypeRequiredDescription
emailstringYesInvitee email address.
workspaceRolestringNoWorkspace-level role to grant on accept. One of workspace_admin. Omit for application-only access.
applicationRolesarrayNoPer-application role assignments. Each entry is { applicationId, role } where role is admin | developer | finance | viewer.
environmentGrantsobjectNoWorkspace-wide environment access. Shape: { grantType: all | all_non_production | production_only | selected, environmentIds?: uuid[] }. selected requires at least one active environment id.

Responses

201 Invitation created and email sent.

Response fields

FieldTypeRequiredDescription
iduuidNoInvitation id.
emailstringNoInvitee email.
statusstringNoAlways pending on creation.
expiresAtISO 8601NoToken expiry, 7 days out.

Common errors

Status Code Cause Fix
403 permission_denied Caller lacks workspace:invite or workspace:invite-admin. Use an owner or workspace_admin token.
403 environment_grants_forbidden A non-owner/non-workspace-admin tried to set explicit environment grants. Ask an owner or workspace admin to set environment access.
400 invalid_environment_grants selected was sent without environmentIds, or ids were sent for a non-selected grant type. Send a valid grantType and include environmentIds only for selected grants.
409 already_member The email address already belongs to an active workspace member. Use the member management endpoints to update roles instead.
GET /api/v1/workspaces/{workspaceId}/invites Setup

List workspace invitations.

Clerk JWT
Implementation details

Returns all invitations for the workspace, including pending, accepted, revoked, and expired. Filterable by status.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.

Query parameters

FieldTypeRequiredDescription
statusstringNoFilter by invitation status. One of pending | accepted | revoked | expired.

Responses

200 Invitation list.

Response fields

FieldTypeRequiredDescription
itemsarrayNoArray of invitation objects.
totalintegerNoTotal matching invitations.
POST /api/v1/workspaces/{workspaceId}/invites/{id}/resend Setup

Resend a pending invitation.

Clerk JWT
Implementation details

Rotates the invitation token (old token is immediately invalidated) and sends a new email with a fresh 7-day expiry.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.
iduuidYesInvitation id.

Responses

200 Token rotated, email resent.

Response fields

FieldTypeRequiredDescription
expiresAtISO 8601NoNew token expiry.

Common errors

Status Code Cause Fix
409 invite_not_pending Invitation is already accepted, revoked, or expired. Create a new invitation.
POST /api/v1/workspaces/{workspaceId}/invites/{id}/revoke Setup

Revoke a pending invitation.

Clerk JWT
Implementation details

Kills the invitation token immediately. The invitee can no longer use the email link to join.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.
iduuidYesInvitation id.

Responses

204 Invitation revoked. No JSON body.
POST /api/v1/invites/accept Setup

Accept a workspace invitation.

Clerk JWT
Implementation details

Validates the signed token, verifies the Clerk user email matches the invited address, creates the workspace member record, and applies role assignments. The token is consumed on success.

Request body

FieldTypeRequiredDescription
tokenstringYesSigned JWT from the invite email link.

Responses

200 Member joined.

Response fields

FieldTypeRequiredDescription
workspaceIduuidNoThe joined workspace.
memberIduuidNoNew workspace member record id.

Common errors

Status Code Cause Fix
400 invite_expired Token TTL has elapsed. Ask an admin to resend the invitation.
400 invite_email_mismatch Clerk account email does not match the invited address. Sign in with the exact email address the invitation was sent to.
409 already_member Caller is already a member of this workspace. No action needed; access is already granted.

Workspace Members

List, inspect, update, and remove workspace members. Member records hold workspace-level role assignments, per-application role assignments, and workspace-wide environment grants.

GET /api/v1/workspaces/{workspaceId}/members Setup

List workspace members.

Clerk JWT Guide
Implementation details

Returns all active members with their workspace role, per-application role assignments, and environment grants. Requires workspace:edit-member or workspace:remove-member permission.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.

Responses

200 Member list.

Response fields

FieldTypeRequiredDescription
itemsarrayNoArray of member objects.
items[].environmentGrantsobjectNoEnvironment grant summary: { grantType, environmentIds }. Owners resolve to all.
totalintegerNoTotal member count.
GET /api/v1/workspaces/{workspaceId}/members/me Setup

Get the calling user's membership.

Clerk JWT
Implementation details

Returns the workspace role, application role assignments, and environment grants for the authenticated caller. No elevated permissions required.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.

Responses

200 Caller's membership record.

Response fields

FieldTypeRequiredDescription
memberIduuidNoMembership record id.
workspaceRolestringNoowner | workspace_admin | null.
applicationRolesarrayNoPer-application role assignments.
environmentGrantsobjectNoEnvironment grant summary for the calling member.
PATCH /api/v1/workspaces/{workspaceId}/members/{memberId} Setup

Update a member role.

Clerk JWT
Implementation details

Change the workspace-level role, update per-application role assignments, or replace environment grants. Send only the fields you want to change. Requires workspace:edit-member; only owner/workspace_admin callers may set environmentGrants.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.
memberIduuidYesMember id.

Request body

FieldTypeRequiredDescription
workspaceRolestringNoNew workspace role. One of workspace_admin or null to remove.
applicationRolesarrayNoPer-application role assignments to upsert. Each entry is { applicationId, role }.
environmentGrantsobjectNoReplace environment access. Shape: { grantType: all | all_non_production | production_only | selected, environmentIds?: uuid[] }.

Responses

200 Updated member record.

Common errors

Status Code Cause Fix
403 permission_denied Caller cannot grant a role higher than their own. Use an owner or workspace_admin token.
403 environment_grants_forbidden Caller cannot manage environment grants. Use an owner or workspace_admin token.
400 invalid_environment_grants Environment grants were malformed. For selected grants, include at least one environment id; omit ids for other grant types.
DELETE /api/v1/workspaces/{workspaceId}/members/{memberId} Setup

Remove a member from the workspace.

Clerk JWT
Implementation details

Revokes all application roles and removes the workspace membership record in one call. Requires workspace:remove-member.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.
memberIduuidYesMember id.

Responses

204 Member removed. No JSON body.

Common errors

Status Code Cause Fix
403 cannot_remove_owner Attempt to remove the workspace owner. Transfer ownership first, then remove the former owner.
DELETE /api/v1/workspaces/{workspaceId}/members/{memberId}/applications/{applicationId} Setup

Revoke a member's access to one application.

Clerk JWT
Implementation details

Removes the per-application role for the specified application. Workspace membership and roles on other applications are unchanged. Requires application:edit-app-member on the target application.

Path parameters

FieldTypeRequiredDescription
workspaceIduuidYesWorkspace id.
memberIduuidYesMember id.
applicationIduuidYesApplication to revoke access on.

Responses

204 Application role revoked. No JSON body.
GET /api/v1/auth/permissions Setup

Resolve permissions for the authenticated caller.

Clerk JWT Guide
Implementation details

Returns every permission key and whether it resolves to true for the caller. Pass x-application-id to include application-level role flags. Useful for building UI conditionals.

Responses

200 Permissions map.

Response fields

FieldTypeRequiredDescription
workspaceRolestringNoCaller workspace role or null.
appRolestringNoCaller application role on x-application-id or null.
permissionsobjectNoMap of permission key → boolean.

Connectors

Manage payment-provider connections and configure card-transaction routing rules. Routing lets you define which providers are tried, in which order, and for which buyer regions. These are dashboard/merchant-facing routes; all require a Clerk JWT and the connectors:read or connectors:write scope.

GET /api/v1/payment-routing Setup

Get the card-transaction routing config.

Clerk JWT
Implementation details

Returns the current routing configuration for the application: a set of condition-based rules and a default cascade. Rules are evaluated in order; the first rule whose conditions all match wins and its cascade is used. Conditions within a rule are AND-ed. The default cascade applies to any transaction not matched by a rule. Within each cascade, providers are tried in order — if the first provider fails, the next is attempted automatically. Routing is now supported in production environments.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Responses

200 Current routing config.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesOrdered list of condition-based routing rules. Each rule is evaluated in order; the first rule whose conditions all match is applied.
data.rules[].conditionsarrayYesArray of conditions that must all be satisfied (AND-ed) for this rule to match. Each condition matches against a specific transaction attribute.
data.rules[].conditions[].typestringYesCondition type: "country" (buyer country), "cardScheme" (card brand), "cardSource" (payment source), "cardType" (credit/debit/prepaid), "cardCountry" (card issuer country), "paymentSource" (payment initiation source), "amount" (transaction amount), "currency" (transaction currency), or "metadata" (transaction metadata key/value matching).
data.rules[].conditions[].opstringYesOperator: for set conditions (country/cardScheme/cardSource/cardType/cardCountry/paymentSource/currency): "is_one_of" or "is_not_one_of". For amount conditions: "gt" (greater than), "lt" (less than), or "between". For metadata conditions: "includes" or "excludes".
data.rules[].conditions[].valuesstring[]NoArray of values for set conditions. For country: ISO 3166-1 alpha-2 codes. For cardScheme: visa, mastercard, amex, etc. For cardSource: raw, token, applepay, googlepay, network-token. For cardType: credit, debit, prepaid. For cardCountry: ISO 3166-1 alpha-2 codes representing the card issuer country (e.g. "US", "GB"). For paymentSource: ecommerce, moto, recurring, installment, card_on_file. For currency: ISO 4217 codes. For metadata conditions: string values to match against the metadata key.
data.rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions. Throttle automatically stamps "throttle_order_type" on every transaction ("one_off" for one-off checkouts, "recurring" for subscription renewals). Any metadata the merchant sends at checkout is also routable by its key.
data.rules[].conditions[].currencystringNoCurrency for amount conditions (e.g., "USD"). Required for amount conditions.
data.rules[].conditions[].minnumberNoMinimum amount in major units (e.g., dollars, not cents) for "gt" or "between" operators.
data.rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators.
data.rules[].cascadestring[]YesOrdered list of connector IDs to use when this rule matches. First entry is attempted first; subsequent entries are automatic fail-over.
data.defaultCascadestring[]YesOrdered list of connector IDs tried for any transaction not matched by any rule. First entry is attempted first; subsequent entries are automatic fail-over.

Common errors

Status Code Cause Fix
403 permission_denied Caller does not have the application:settings permission. Use a Clerk session with admin or owner role on the target application.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. Check provider status if the error persists.
Get routing config
curl https://api.usethrottle.dev/api/v1/payment-routing \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>"
Example response
{
  "data": {
    "rules": [
      {
        "conditions": [
          { "type": "country", "op": "is_one_of", "values": ["CA", "MX"] }
        ],
        "cascade": ["connector-id-north-america", "connector-id-backup"]
      }
    ],
    "defaultCascade": ["connector-id-stripe", "connector-id-backup"]
  }
}

Production notes

  • Routing is supported in both production and non-production environments.
  • Rules are evaluated in order; the first rule whose conditions all match is applied.
  • Conditions within a rule are AND-ed together.
  • Use GET /api/v1/routing-connectors to list connector IDs eligible for a cascade.
PUT /api/v1/payment-routing Setup

Replace the card-transaction routing config.

Clerk JWT
Implementation details

Atomically replaces the full routing configuration using condition-based rules. Supply the complete desired state; the previous config is discarded and rebuilt from the supplied body. All connector IDs must be active connectors for the application. Condition values must conform to the allowed vocabulary for each condition type. Routing is now supported in production environments.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
rulesarrayYesArray of condition-based routing rules. May be empty to use only the default cascade globally. Rules are evaluated in order; the first rule whose conditions all match is applied.
rules[].conditionsarrayYesArray of conditions for this rule. All conditions must be satisfied (AND-ed) for the rule to match. May be empty.
rules[].conditions[].typestringYesCondition type: "country", "cardScheme", "cardSource", "cardType", "cardCountry", "paymentSource", "amount", or "metadata".
rules[].conditions[].opstringYesOperator. For set types (country/cardScheme/cardSource/cardType/cardCountry/paymentSource): "is_one_of" or "is_not_one_of". For amount: "gt" (greater than), "lt" (less than), or "between". For metadata: "includes" or "excludes".
rules[].conditions[].valuesstring[]NoFor set conditions: array of allowed values. For country: ISO 3166-1 alpha-2 codes (US, CA, GB, etc.). For cardScheme: visa, mastercard, amex, discover, diners-club, jcb, maestro, unionpay, etc. For cardSource: raw, token, applepay, googlepay, network-token. For cardType: credit, debit, prepaid. For cardCountry: ISO 3166-1 alpha-2 codes representing the card issuer country (e.g. "US", "GB"). For paymentSource: ecommerce, moto, recurring, installment, card_on_file. For currency: ISO 4217 codes. For metadata conditions: string values to match against the metadata key.
rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions. Throttle automatically stamps "throttle_order_type" on every transaction ("one_off" for one-off checkouts, "recurring" for subscription renewals). Any metadata the merchant sends at checkout is also routable by its key.
rules[].conditions[].currencystringNoCurrency code for amount conditions (e.g., "USD"). Required for amount type.
rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators. Required for those operators.
rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators. Required for those operators.
rules[].cascadestring[]YesOrdered list of connector IDs for this rule. Must contain at least one entry and must not have duplicates.
defaultCascadestring[]YesOrdered connector IDs tried for any transaction not matched by any rule. Must contain at least one entry and must not have duplicates.

Responses

200 Updated routing config returned.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesThe persisted rules after the update.
data.defaultCascadestring[]YesThe persisted default cascade after the update.

Common errors

Status Code Cause Fix
422 routing_default_required defaultCascade is empty. Include at least one active connector ID in defaultCascade.
422 routing_unknown_connector A connector ID in a cascade does not exist or is not active for this application. List available connectors with GET /api/v1/routing-connectors and use only those IDs.
422 routing_duplicate_connector A connector ID appears more than once within the same cascade. Each connector may appear at most once per cascade.
422 routing_empty_cascade A rule cascade is empty. Each rule must include at least one connector ID.
422 routing_rule_no_conditions A routing rule has no conditions. Each rule must include at least one condition.
422 routing_empty_condition_values A set-type condition (country, cardScheme, cardSource, cardType) has an empty values array. Include at least one value in the condition values array.
422 routing_invalid_country A country condition value is not a valid ISO 3166-1 alpha-2 code. Use two-letter uppercase country codes, e.g. "US", "CA".
422 routing_invalid_currency An amount condition currency is not a valid ISO-4217 code. Use three-letter uppercase currency codes, e.g. "USD", "EUR".
422 routing_invalid_amount An amount condition is missing a required field (min for gt, max for lt, both for between) or a value is not a positive number. Provide the required numeric fields for the amount operator and ensure all values are positive.
422 routing_amount_range_invalid An amount between condition has min >= max. Ensure min is strictly less than max.
422 routing_invalid_scheme A cardScheme condition value is not in the allowed card scheme vocabulary. Use one of the documented card scheme values (e.g. "visa", "mastercard", "amex").
422 routing_invalid_card_source A cardSource condition value is not in the allowed card source vocabulary. Use one of: applepay, googlepay, network-token, raw, token.
422 routing_invalid_card_type A cardType condition value is not in the allowed card type vocabulary. Use one of: credit, debit, prepaid.
422 routing_invalid_condition An unsupported condition type was supplied. Use one of: country, cardScheme, cardSource, cardType, cardCountry, paymentSource, amount.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. The routing config was not partially applied.
Set routing config
curl -X PUT https://api.usethrottle.dev/api/v1/payment-routing \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>" \
  -H "content-type: application/json" \
  -d '{
    "rules": [
      {
        "conditions": [
          { "type": "country", "op": "is_one_of", "values": ["CA", "MX"] }
        ],
        "cascade": ["connector-id-north-america", "connector-id-backup"]
      }
    ],
    "defaultCascade": ["connector-id-stripe", "connector-id-backup"]
  }'

Production notes

  • This is a full replacement, not a patch. Omitting a rule removes it.
  • Rules are evaluated in order; the first rule whose conditions all match is applied.
  • Conditions within a rule are AND-ed together.
  • The fail-over order within each cascade is deterministic: the first connector in the array is attempted first, and subsequent entries are tried only if earlier ones fail.
  • Routing is supported in both production and non-production environments.
GET /api/v1/routing-connectors Setup

List connectors eligible for a routing cascade.

Clerk JWT
Implementation details

Returns the active connectors that may be referenced in the defaultCascade or any rule cascade arrays. Only connectors with an active status are included. Use the returned id values when constructing or validating a routing config.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Responses

200 List of available connectors.

Response fields

FieldTypeRequiredDescription
data[].idstringYesConnector ID to use in cascade arrays.
data[].displayNamestringYesHuman-readable connector label.
data[].methodstringNoPayment method type such as card.

Common errors

Status Code Cause Fix
403 permission_denied Caller does not have the application:settings permission. Use a Clerk session with admin or owner role on the target application.
List available connectors
curl https://api.usethrottle.dev/api/v1/routing-connectors \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>"
Example response
{
  "data": [
    { "id": "connector-id-stripe", "displayName": "Stripe (live)", "method": "card" },
    { "id": "connector-id-backup", "displayName": "Backup processor", "method": "card" }
  ]
}

Production notes

  • This endpoint always reflects the current active connectors. Re-fetch before building a new routing config to avoid referencing deleted or inactive connectors.
GET /api/v1/routing-payment-methods Setup

List payment methods available to routing and decline rules.

Clerk JWT
Implementation details

Returns the payment methods supported for this environment. Use a returned id as the value of a paymentMethod condition (Other-transaction decline rules) or as the method of an Other-transaction routing rule. Card methods (card, network-token) are excluded — card routing and decline use card-specific conditions instead.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Responses

200 List of available payment methods.

Response fields

FieldTypeRequiredDescription
data[].idstringYesPayment method id to use as a paymentMethod condition value or an Other-routing rule method (e.g. "paypal", "ideal", "sepa").
data[].labelstringYesHuman-readable payment method label.

Common errors

Status Code Cause Fix
403 permission_denied Caller does not have the application:settings permission. Use a Clerk session with admin or owner role on the target application.
502 payment_provider_error The upstream payment provider method-definitions API returned an unexpected error. Retry after a short delay. Check provider status if the error persists.
List available payment methods
curl https://api.usethrottle.dev/api/v1/routing-payment-methods \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>"
Example response
{
  "data": [
    { "id": "paypal", "label": "PayPal" },
    { "id": "ideal", "label": "iDEAL" },
    { "id": "sepa", "label": "SEPA" }
  ]
}

Production notes

  • Card and network-token methods are excluded; card routing and decline use card-specific conditions (cardScheme, cardType, cardCountry, etc.).
GET /api/v1/card-decline Setup

Get the card-transaction decline rules.

Clerk JWT
Implementation details

Returns the current set of decline rules for card transactions. Rules are evaluated in order before routing occurs; the first rule whose conditions all match rejects the transaction outright and, when an errorCode is configured, returns that code to the integration. Conditions within a rule are AND-ed. A transaction that matches no decline rule proceeds to the routing step.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Responses

200 Current card-decline rules.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesOrdered list of decline rules. Each rule is evaluated in order; the first rule whose conditions all match causes the transaction to be declined.
data.rules[].conditionsarrayYesArray of conditions that must all be satisfied (AND-ed) for this rule to match. Allowed condition types: country, cardScheme, cardSource, cardType, cardCountry, amount, currency, metadata.
data.rules[].conditions[].typestringYesCondition type: "country" (buyer country), "cardScheme" (card brand), "cardSource" (payment source), "cardType" (credit/debit/prepaid), "cardCountry" (card issuer country), "amount" (transaction amount), "currency" (transaction currency), or "metadata" (transaction metadata key/value matching).
data.rules[].conditions[].opstringYesOperator: for set conditions (country/cardScheme/cardSource/cardType/cardCountry/currency): "is_one_of" or "is_not_one_of". For amount: "gt", "lt", or "between". For metadata: "includes" or "excludes".
data.rules[].conditions[].valuesstring[]NoArray of values for set conditions. For country: ISO 3166-1 alpha-2 codes. For cardScheme: visa, mastercard, amex, etc. For cardSource: raw, token, applepay, googlepay, network-token. For cardType: credit, debit, prepaid. For cardCountry: ISO 3166-1 alpha-2 codes representing the card issuer country (e.g. "US", "GB"). For currency: ISO 4217 codes. For metadata conditions: string values to match against the metadata key.
data.rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions.
data.rules[].conditions[].currencystringNoCurrency for amount conditions (e.g., "USD"). Required for amount conditions.
data.rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators.
data.rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators.
data.rules[].errorCodestringNoOptional error code returned to the integration when this rule declines the transaction. Must match ^flow_[a-z_]+$ (e.g. "flow_high_risk_country"). If omitted, a generic decline code is returned.

Common errors

Status Code Cause Fix
403 permission_denied Caller does not have the application:settings permission. Use a Clerk session with admin or owner role on the target application.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. Check provider status if the error persists.
Get card-decline rules
curl https://api.usethrottle.dev/api/v1/card-decline \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>"
Example response
{
  "data": {
    "rules": [
      {
        "conditions": [
          { "type": "country", "op": "is_one_of", "values": ["RU", "KP"] }
        ],
        "errorCode": "flow_high_risk_country"
      }
    ]
  }
}

Production notes

  • Decline rules are evaluated before routing. A matched rule rejects the transaction immediately.
  • Rules are evaluated in order; the first matching rule wins.
  • Conditions within a rule are AND-ed together.
  • errorCode is optional. When set it must match ^flow_[a-z_]+$.
PUT /api/v1/card-decline Setup

Replace the card-transaction decline rules.

Clerk JWT
Implementation details

Atomically replaces the full set of card-transaction decline rules. Supply the complete desired state; previous rules are discarded. A matched rule rejects the transaction before routing; the optional errorCode is returned to the integration. Each rule requires at least one condition.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
rulesarrayYesArray of decline rules. May be empty to disable all decline rules. Rules are evaluated in order.
rules[].conditionsarrayYesArray of conditions for this rule. All conditions must be satisfied (AND-ed) for the rule to match. Must contain at least one condition.
rules[].conditions[].typestringYesCondition type: "country", "cardScheme", "cardSource", "cardType", "cardCountry", "amount", or "metadata".
rules[].conditions[].opstringYesOperator. For set types (country/cardScheme/cardSource/cardType/cardCountry): "is_one_of" or "is_not_one_of". For amount: "gt", "lt", or "between". For metadata: "includes" or "excludes".
rules[].conditions[].valuesstring[]NoFor set conditions: array of allowed values. For country: ISO 3166-1 alpha-2 codes. For cardScheme: visa, mastercard, amex, discover, diners-club, jcb, maestro, unionpay, etc. For cardSource: raw, token, applepay, googlepay, network-token. For cardType: credit, debit, prepaid. For cardCountry: ISO 3166-1 alpha-2 codes representing the card issuer country (e.g. "US", "GB"). For metadata: string values to match against the metadata key.
rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions.
rules[].conditions[].currencystringNoCurrency code for amount conditions (e.g., "USD"). Required for amount type.
rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators. Required for those operators.
rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators. Required for those operators.
rules[].errorCodestringNoOptional error code returned to the integration when this rule fires. Must match ^flow_[a-z_]+$ (e.g. "flow_high_risk_country"). No free-text message field — errorCode only.

Responses

200 Updated card-decline rules returned.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesThe persisted decline rules after the update.

Common errors

Status Code Cause Fix
422 decline_rule_no_conditions A decline rule has no conditions. Each rule must include at least one condition.
422 routing_invalid_country A country condition value is not a valid ISO 3166-1 alpha-2 code. Use two-letter uppercase country codes, e.g. "US", "CA".
422 routing_invalid_currency An amount condition currency is not a valid ISO-4217 code. Use three-letter uppercase currency codes, e.g. "USD", "EUR".
422 routing_invalid_amount An amount condition is missing a required field or a value is not a positive number. Provide the required numeric fields for the amount operator and ensure all values are positive.
422 routing_amount_range_invalid An amount between condition has min >= max. Ensure min is strictly less than max.
422 routing_invalid_scheme A cardScheme condition value is not in the allowed card scheme vocabulary. Use one of the documented card scheme values (e.g. "visa", "mastercard", "amex").
422 routing_invalid_card_source A cardSource condition value is not in the allowed card source vocabulary. Use one of: applepay, googlepay, network-token, raw, token.
422 routing_invalid_card_type A cardType condition value is not in the allowed card type vocabulary. Use one of: credit, debit, prepaid.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. The decline rules were not partially applied.
Set card-decline rules
curl -X PUT https://api.usethrottle.dev/api/v1/card-decline \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>" \
  -H "content-type: application/json" \
  -d '{
    "rules": [
      {
        "conditions": [
          { "type": "country", "op": "is_one_of", "values": ["RU", "KP"] }
        ],
        "errorCode": "flow_high_risk_country"
      },
      {
        "conditions": [
          { "type": "cardType", "op": "is_one_of", "values": ["prepaid"] },
          { "type": "amount", "op": "gt", "currency": "USD", "min": 500 }
        ]
      }
    ]
  }'

Production notes

  • This is a full replacement, not a patch. Omitting a rule removes it.
  • Rules are evaluated in order before routing; the first matching rule declines the transaction.
  • Conditions within a rule are AND-ed together.
  • errorCode must match ^flow_[a-z_]+$ when provided. There is no free-text message field.
  • Send an empty rules array to remove all decline rules.
GET /api/v1/other-routing Setup

Get the non-card ("Other transactions") routing config.

Clerk JWT
Implementation details

Returns the current routing configuration for non-card payment methods (e.g. PayPal, iDEAL, SEPA). Rules are evaluated in order; the first rule whose method and conditions all match determines the connector cascade used. Unmatched non-card transactions fall through to the default (first active connection for the payment method and currency).

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Responses

200 Current non-card routing config.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesOrdered list of non-card routing rules. Each rule is evaluated in order; the first rule whose method and conditions all match is applied.
data.rules[].methodstringYesPayment method this rule applies to (e.g. "paypal", "ideal", "sepa"). Only transactions of this method are tested against this rule's conditions.
data.rules[].conditionsarrayYesArray of conditions that must all be satisfied (AND-ed) for this rule to match. Allowed condition types: country, currency, amount, metadata. Card conditions (cardScheme, cardSource, cardType) are not permitted here.
data.rules[].conditions[].typestringYesCondition type: "country" (buyer country), "currency" (transaction currency), "amount" (transaction amount), or "metadata" (transaction metadata key/value matching). Card-specific condition types are not permitted for non-card routing rules.
data.rules[].conditions[].opstringYesOperator: for set conditions (country/currency): "is_one_of" or "is_not_one_of". For amount: "gt", "lt", or "between". For metadata: "includes" or "excludes".
data.rules[].conditions[].valuesstring[]NoArray of values for set conditions. For country: ISO 3166-1 alpha-2 codes. For currency: ISO 4217 codes (e.g. "USD", "EUR"). For metadata: string values to match against the metadata key.
data.rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions.
data.rules[].conditions[].currencystringNoCurrency for amount conditions (e.g., "USD"). Required for amount conditions.
data.rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators.
data.rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators.
data.rules[].connectionsstring[]YesOrdered list of connector IDs of the same method as the rule. First entry is attempted first; subsequent entries are automatic fail-over. All connectors must use the same payment method as rules[].method.

Common errors

Status Code Cause Fix
403 permission_denied Caller does not have the application:settings permission. Use a Clerk session with admin or owner role on the target application.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. Check provider status if the error persists.
Get non-card routing config
curl https://api.usethrottle.dev/api/v1/other-routing \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>"
Example response
{
  "data": {
    "rules": [
      {
        "method": "paypal",
        "conditions": [
          { "type": "country", "op": "is_one_of", "values": ["US", "CA"] }
        ],
        "connections": ["connector-id-paypal-primary", "connector-id-paypal-backup"]
      }
    ]
  }
}

Production notes

  • Rules are evaluated in order; the first rule whose method and conditions all match is applied.
  • Conditions within a rule are AND-ed together.
  • Card-specific condition types (cardScheme, cardSource, cardType) are not permitted. Use method to identify the payment method.
  • Unmatched non-card transactions use the default (first active connection for that method and currency). There is no pinned default cascade for this config.
  • Use GET /api/v1/routing-connectors to discover connector IDs and their associated payment methods.
PUT /api/v1/other-routing Setup

Replace the non-card ("Other transactions") routing config.

Clerk JWT
Implementation details

Atomically replaces the full routing configuration for non-card payment methods. Supply the complete desired state; previous rules are discarded. Each rule must declare a payment method and at least one connector of that method. Card conditions (cardScheme, cardSource, cardType) are not permitted — use the method field instead.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
rulesarrayYesArray of non-card routing rules. May be empty to disable custom non-card routing. Rules are evaluated in order.
rules[].methodstringYesPayment method this rule targets (e.g. "paypal", "ideal", "sepa"). Must be a non-card method. All connections in this rule must be of the same method.
rules[].conditionsarrayYesArray of conditions for this rule. All conditions must be satisfied (AND-ed) for the rule to match. May be empty (matches all transactions of the given method).
rules[].conditions[].typestringYesCondition type: "country", "currency", "amount", or "metadata". Card-specific types (cardScheme, cardSource, cardType) are not permitted.
rules[].conditions[].opstringYesOperator. For set types (country/currency): "is_one_of" or "is_not_one_of". For amount: "gt", "lt", or "between". For metadata: "includes" or "excludes".
rules[].conditions[].valuesstring[]NoFor set conditions: array of allowed values. For country: ISO 3166-1 alpha-2 codes. For currency: ISO 4217 codes (e.g. "USD", "EUR"). For metadata: string values to match against the metadata key.
rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions.
rules[].conditions[].currencystringNoCurrency code for amount conditions (e.g., "USD"). Required for amount type.
rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators. Required for those operators.
rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators. Required for those operators.
rules[].connectionsstring[]YesOrdered list of connector IDs for this rule. All connectors must use the same payment method as rules[].method. Must contain at least one entry.

Responses

200 Updated non-card routing config returned.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesThe persisted non-card routing rules after the update.

Common errors

Status Code Cause Fix
422 other_routing_method_required A rule is missing the method field. Provide a non-card payment method string (e.g. "paypal", "ideal") for each rule.
422 other_routing_empty_connections A rule has an empty connections array. Each rule must include at least one connector ID in connections.
422 other_routing_method_mismatch A connection's payment method does not match the rule's method field. Ensure every connector ID in a rule's connections uses the same payment method as the rule's method field.
422 other_routing_unknown_connector A connector ID in connections does not exist or is not active for this application. List available connectors with GET /api/v1/routing-connectors and use only active connector IDs.
422 routing_invalid_country A country condition value is not a valid ISO 3166-1 alpha-2 code. Use two-letter uppercase country codes, e.g. "US", "CA".
422 routing_invalid_currency A currency condition or amount condition currency is not a valid ISO-4217 code. Use three-letter uppercase currency codes, e.g. "USD", "EUR".
422 routing_invalid_amount An amount condition is missing a required field or a value is not a positive number. Provide the required numeric fields for the amount operator and ensure all values are positive.
422 routing_amount_range_invalid An amount between condition has min >= max. Ensure min is strictly less than max.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. The routing config was not partially applied.
Set non-card routing config
curl -X PUT https://api.usethrottle.dev/api/v1/other-routing \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>" \
  -H "content-type: application/json" \
  -d '{
    "rules": [
      {
        "method": "paypal",
        "conditions": [
          { "type": "country", "op": "is_one_of", "values": ["US", "CA"] }
        ],
        "connections": ["connector-id-paypal-primary", "connector-id-paypal-backup"]
      },
      {
        "method": "ideal",
        "conditions": [],
        "connections": ["connector-id-ideal-primary"]
      }
    ]
  }'

Production notes

  • This is a full replacement, not a patch. Omitting a rule removes it.
  • Rules are evaluated in order; the first rule whose method and conditions all match is applied.
  • Conditions within a rule are AND-ed together.
  • Card-specific condition types (cardScheme, cardSource, cardType) are not permitted. The method field identifies the payment method.
  • Unmatched non-card transactions use the default. There is no pinned default cascade.
  • Send an empty rules array to remove all custom non-card routing rules.
GET /api/v1/other-decline Setup

Get the non-card transaction decline rules.

Clerk JWT
Implementation details

Returns the current set of decline rules for non-card payment method transactions. Rules are evaluated in order before routing occurs; the first rule whose conditions all match rejects the transaction. Allowed condition types are paymentMethod, country, currency, amount, and metadata.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.

Responses

200 Current non-card decline rules.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesOrdered list of non-card decline rules. The first rule whose conditions all match causes the transaction to be declined.
data.rules[].conditionsarrayYesArray of conditions that must all be satisfied (AND-ed) for this rule to match. Allowed condition types: paymentMethod, country, currency, amount, metadata.
data.rules[].conditions[].typestringYesCondition type: "paymentMethod" (specific non-card method), "country" (buyer country), "currency" (transaction currency), "amount" (transaction amount), or "metadata" (transaction metadata key/value matching).
data.rules[].conditions[].opstringYesOperator: for paymentMethod/country/currency conditions: "is_one_of" or "is_not_one_of". For amount: "gt", "lt", or "between". For metadata: "includes" or "excludes".
data.rules[].conditions[].valuesstring[]NoArray of values for set conditions. For paymentMethod: non-card method names (e.g. "paypal", "ideal", "sepa"). For country: ISO 3166-1 alpha-2 codes. For currency: ISO 4217 codes. For metadata: string values to match against the metadata key.
data.rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions.
data.rules[].conditions[].currencystringNoCurrency for amount conditions (e.g., "USD"). Required for amount conditions.
data.rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators.
data.rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators.
data.rules[].errorCodestringNoOptional error code returned to the integration when this rule declines the transaction. Must match ^flow_[a-z_]+$. If omitted, a generic decline code is returned.

Common errors

Status Code Cause Fix
403 permission_denied Caller does not have the application:settings permission. Use a Clerk session with admin or owner role on the target application.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. Check provider status if the error persists.
Get non-card decline rules
curl https://api.usethrottle.dev/api/v1/other-decline \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>"
Example response
{
  "data": {
    "rules": [
      {
        "conditions": [
          { "type": "paymentMethod", "op": "is_one_of", "values": ["paypal"] },
          { "type": "country", "op": "is_one_of", "values": ["RU"] }
        ],
        "errorCode": "flow_high_risk_country"
      }
    ]
  }
}

Production notes

  • Decline rules are evaluated before routing. A matched rule rejects the transaction immediately.
  • Rules are evaluated in order; the first matching rule wins.
  • Conditions within a rule are AND-ed together.
  • The paymentMethod condition type uses "is_one_of" or "is_not_one_of" operators.
  • errorCode is optional. When set it must match ^flow_[a-z_]+$.
PUT /api/v1/other-decline Setup

Replace the non-card transaction decline rules.

Clerk JWT
Implementation details

Atomically replaces the full set of decline rules for non-card payment method transactions. Supply the complete desired state; previous rules are discarded. Each rule requires at least one condition. Allowed condition types are paymentMethod, country, currency, amount, and metadata.

Headers

FieldTypeRequiredDescription
authorizationBearer <dashboard-session>YesClerk dashboard session token. API keys cannot call workspace-level routes.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
rulesarrayYesArray of non-card decline rules. May be empty to disable all non-card decline rules. Rules are evaluated in order.
rules[].conditionsarrayYesArray of conditions for this rule. All conditions must be satisfied (AND-ed) for the rule to match. Must contain at least one condition.
rules[].conditions[].typestringYesCondition type: "paymentMethod", "country", "currency", "amount", or "metadata".
rules[].conditions[].opstringYesOperator. For paymentMethod/country/currency: "is_one_of" or "is_not_one_of". For amount: "gt", "lt", or "between". For metadata: "includes" or "excludes".
rules[].conditions[].valuesstring[]NoFor set conditions: array of allowed values. For paymentMethod: non-card method names (e.g. "paypal", "ideal", "sepa"). For country: ISO 3166-1 alpha-2 codes. For currency: ISO 4217 codes. For metadata: string values to match against the metadata key.
rules[].conditions[].keystringNoMetadata key to match against. Required for metadata conditions.
rules[].conditions[].currencystringNoCurrency code for amount conditions (e.g., "USD"). Required for amount type.
rules[].conditions[].minnumberNoMinimum amount in major units for "gt" or "between" operators. Required for those operators.
rules[].conditions[].maxnumberNoMaximum amount in major units for "lt" or "between" operators. Required for those operators.
rules[].errorCodestringNoOptional error code returned to the integration when this rule fires. Must match ^flow_[a-z_]+$. No free-text message field — errorCode only.

Responses

200 Updated non-card decline rules returned.

Response fields

FieldTypeRequiredDescription
data.rulesarrayYesThe persisted non-card decline rules after the update.

Common errors

Status Code Cause Fix
422 decline_rule_no_conditions A decline rule has no conditions. Each rule must include at least one condition.
422 routing_invalid_country A country condition value is not a valid ISO 3166-1 alpha-2 code. Use two-letter uppercase country codes, e.g. "US", "CA".
422 routing_invalid_currency A currency condition or amount condition currency is not a valid ISO-4217 code. Use three-letter uppercase currency codes, e.g. "USD", "EUR".
422 routing_invalid_amount An amount condition is missing a required field or a value is not a positive number. Provide the required numeric fields for the amount operator and ensure all values are positive.
422 routing_amount_range_invalid An amount between condition has min >= max. Ensure min is strictly less than max.
502 payment_provider_error The upstream payment provider returned an unexpected error. Retry after a short delay. The decline rules were not partially applied.
Set non-card decline rules
curl -X PUT https://api.usethrottle.dev/api/v1/other-decline \
  -H "authorization: Bearer <clerk-session-token>" \
  -H "x-application-id: <application-uuid>" \
  -H "x-throttle-environment-id: <environment-uuid>" \
  -H "content-type: application/json" \
  -d '{
    "rules": [
      {
        "conditions": [
          { "type": "paymentMethod", "op": "is_one_of", "values": ["paypal"] },
          { "type": "country", "op": "is_one_of", "values": ["RU", "KP"] }
        ],
        "errorCode": "flow_high_risk_country"
      }
    ]
  }'

Production notes

  • This is a full replacement, not a patch. Omitting a rule removes it.
  • Rules are evaluated in order before routing; the first matching rule declines the transaction.
  • Conditions within a rule are AND-ed together.
  • The paymentMethod condition type uses "is_one_of" or "is_not_one_of" to target specific non-card methods.
  • errorCode must match ^flow_[a-z_]+$ when provided. There is no free-text message field.
  • Send an empty rules array to remove all non-card decline rules.

Analytics and Export

Read-only merchant reporting: an orders + gross-revenue time series and a CSV export of orders. Both require the orders:read scope (secret API key or dashboard session) and are scoped to the caller’s workspace, application, and environment.

GET /api/v1/analytics/orders-timeseries Server-side

Orders + captured gross revenue bucketed by day, week, or month over a date range.

Secret API key or dashboard session
Implementation details

Returns a zero-filled series of order count and captured gross revenue, bucketed by day/week/month in the requested timezone. "Gross" counts orders whose payment status is authorized, captured, partially_paid, partially_refunded, or refunded. `days` remains supported as shorthand for `from = to - days`.

Query parameters

FieldTypeRequiredDescription
fromstringNoISO 8601 start of range, exclusive. Defaults to 30 days before "to" (or "to" minus `days`, if given).
tostringNoISO 8601 end of range, exclusive.
Default: now
daysintegerNoLegacy shorthand for from = to - days. Ignored when from is set.
Default: 30
groupBystringNoBucket size: day, week, or month.
Default: day
timezonestringNoIANA timezone name or UTC offset for bucket boundaries, e.g. America/New_York.
Default: UTC
compareTostringNoAdd a comparison total: previous_period, previous_year, or none.
Default: none

Responses

200 Envelope { data: { days, range, currency, totalOrders, totalGrossCents, series, comparison?, note } } where `range` is { from, to, timezone, groupBy }, each series entry is { date: "YYYY-MM-DD", orders, grossCents }, and `comparison` (present only when compareTo is set) is { range: { from, to }, totalOrders, totalGrossCents }.
GET /api/v1/orders/export.csv Server-side

Export orders as a CSV attachment.

Secret API key or dashboard session
Implementation details

Streams orders as text/csv (Content-Disposition: attachment), honoring the same filters as GET /api/v1/orders. Capped at 10,000 rows. Columns: order_number, id, status, display_status, payment_status, total, comp_amount, currency, customer_id, created_at, delivery_state, subscription_ids (every linked subscription, space-separated, in line order).

Query parameters

FieldTypeRequiredDescription
statusstringNoFilter by order status.
paymentStatusstringNoFilter by payment status.
qstringNoSearch across id, order number, status, and payment status.

Responses

200 A text/csv attachment named orders-export.csv (not the JSON envelope).

Returns and RMA

Open and process returns (RMAs) against an order. A return covers a subset of the order’s line items; on completion a refund is issued for the returned value (capped at the captured amount) and the restock flag signals your WMS. A return can instead be completed as an exchange (create a replacement order + settle the price difference). Requires order_returns:read / order_returns:write.

POST /api/v1/orders/{id}/returns Server-side

Open a return (RMA) against an order.

X-API-Key
Implementation details

Creates a return in status "requested" for a subset of the order’s line items. Validates each line item belongs to the order and the quantity does not exceed the returnable balance (ordered minus already-returned). refundAmount is computed from the line items’ unit prices.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Request body

FieldTypeRequiredDescription
itemsarrayYesLine items to return: [{ lineItemId, quantity }].
reasonstringNoOptional return reason.
restockbooleanNoSignal your WMS to restock on completion. Default false.

Responses

200 The created return with its items.
Open a return
curl -X POST https://api.usethrottle.dev/api/v1/orders/ord_abc/returns \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "items": [{ "lineItemId": "li_1", "quantity": 1 }], "reason": "defective", "restock": true }'
GET /api/v1/orders/{id}/returns Server-side

List an order's returns.

X-API-Key
Implementation details

Returns all RMAs opened against the order, each with its line items.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesOrder id.

Responses

200 Array of returns.
GET /api/v1/returns/{id} Server-side

Get a return and its items.

X-API-Key
Implementation details

Fetches a single return by id, including its line items and current status.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesReturn id.

Responses

200 The return with its items.
POST /api/v1/returns/{id}/transition Server-side

Transition a return through its lifecycle.

X-API-Key
Implementation details

Moves the return: approve (requested→approved), reject (requested→rejected), receive (approved→received), complete (received→completed), cancel (requested|approved→cancelled). On complete a refund is issued for refundAmount against the order’s captured payment and refundPaymentId is stamped. Emits return.updated.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesReturn id.

Request body

FieldTypeRequiredDescription
actionstringYesThe transition to apply.
Values: approve, reject, receive, complete, cancel

Responses

200 The updated return.
409 invalid_return_state — the action is not allowed from the current status.
POST /api/v1/returns/{id}/exchange Server-side

Complete a return as an exchange.

X-API-Key
Implementation details

Creates a replacement order for the swapped items and settles the price difference vs the return’s refund value: if the replacement costs more the difference is charged to the customer’s stored card; if less, it is refunded to the original payment. The return is marked completed and linked to the replacement order (exchange_order_id). The return must be in approved or received status.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesReturn id.

Request body

FieldTypeRequiredDescription
itemsarrayYesReplacement line items: [{ name, unitPrice, quantity, referenceId? }].

Responses

200 The completed return with an exchange { replacementOrderId, replacementTotal, delta, chargedPaymentId?, refundedPaymentId? }.
402 payment_failed — the exchange-difference charge was declined.
409 invalid_return_state — the return is not approved/received.

Bulk Imports

Import a merchant’s historical customers or orders from a CSV or TSV export. The file never passes through the API: POST /api/v1/imports returns a presigned PUT and the browser uploads straight to storage.

POST /api/v1/imports Server-side

Create an import and get a presigned upload URL.

X-API-Key
Implementation details

Creates the import record at status `created` and returns a one-hour presigned PUT for the file. Upload the file with exactly the returned `contentType` — the signature covers that header, so a different one fails with SignatureDoesNotMatch. The 500 MB size ceiling is checked here, before the URL is issued, because a presigned PUT cannot enforce one on its own. Spreadsheets (.xlsx / .xls) are not supported: export the sheet as CSV.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Request body

FieldTypeRequiredDescription
entityTypestringYesWhat the file contains.
Values: customer, order
fileNamestringYesThe merchant’s own file name. Its extension selects the parser: .csv or .tsv. Spreadsheets are rejected.
fileBytesintegerYesSize of the file about to be uploaded, in bytes.
providerstring | nullNoSource platform hint (e.g. `bigcommerce`), used to disambiguate template matching. Omit for a generic export.

Responses

201 Import created; upload the file next.

Response fields

FieldTypeRequiredDescription
importIduuidNoUse this for every later import call.
uploadUrlurlNoPresigned PUT. Send the file body here.
fileKeystringNoStorage key the file will live at.
contentTypestringNoThe Content-Type the PUT must send, verbatim.
expiresInintegerNoSeconds the upload URL stays valid.

Common errors

Status Code Cause Fix
400 unsupported_file_type fileName has an extension the importer cannot read, including .xlsx and .xls. Export the file as CSV (or TSV) and upload that. There is no spreadsheet reader.
413 file_too_large fileBytes exceeds the 500 MB ceiling. Split the export into smaller files. The message names both the limit and the size you sent.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
Create then upload
curl -X POST https://api.usethrottle.dev/api/v1/imports \
  -H "x-api-key: $THROTTLE_API_KEY" \
  -H "content-type: application/json" \
  -d '{"entityType":"order","fileName":"orders.csv","fileBytes":204800,"provider":"bigcommerce"}'

# then PUT the file to data.uploadUrl with the returned content type
curl -X PUT "$UPLOAD_URL" -H "content-type: text/csv" --data-binary @orders.csv
POST /api/v1/imports/{id}/inspect Server-side

Sniff the uploaded file and suggest a mapping.

X-API-Key
Implementation details

Reads at most the first 1 MB of the uploaded file, however large it is, so it answers immediately. Returns the sniffed dialect, the resolved headers, a 20-row sample, the matched platform template and a suggested target field per header. Persists the dialect and the object’s REAL size, and moves the import to status `mapping`. **This retracts any dry run** (`dryRunAt` is cleared): the dialect decides how every row is parsed, so a dry run that ran under a different one described a different file. An import that has already written rows — a finished commit, or one that died partway — cannot be inspected at all, because resetting its status would leave those rows with no way to remove them. A zero-byte object is refused: that is a PUT that did not carry the file.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Responses

200 What the mapping screen needs.

Response fields

FieldTypeRequiredDescription
dialectobjectNo{ delimiter, enclosure, hasHeader } — sniffed, not assumed.
headersstring[]NoColumn names, in file order.
sampleobject[]NoUp to 20 rows: { rowNumber, cells, raw }.
templateobject | nullNoThe matched platform template, or null for a generic export.
suggestionsobjectNoSource header to suggested target field, with a confidence score.

Common errors

Status Code Cause Fix
409 file_not_uploaded The import exists but nothing has been PUT to its upload URL yet — or the object is there and zero bytes, which is a PUT that did not carry the file. Upload the file to the presigned URL from POST /api/v1/imports, then inspect again.
409 import_running A worker is currently validating, committing or undoing this import. Wait for the run to finish. Re-inspecting mid-run would move the status out from under the worker.
409 already_committed The import has already committed, or has been undone. Create a new import. Re-inspecting a settled one would reset it to `mapping` and take its undo path with it.
409 rows_already_written A COMMIT of this import has written rows to this environment — typically one that died partway, which sits at `failed` rather than at a terminal status. A passing dry run does NOT count: it reports what it would create in the same counters, and `lastPassMode` is what tells the two apart. POST /api/v1/imports/{id}/undo to remove them first, or create a new import. Re-inspecting would reset the counters and status those rows are tracked by, leaving them unreachable.
413 file_too_large The uploaded object is over the 500 MB ceiling. Checked here against its REAL size, because a presigned PUT signs only the key and the content type — never the body length — so the size checked at create time was the one the client declared. Split the export and import it in parts.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
PATCH /api/v1/imports/{id}/mapping Server-side

Save the confirmed column mapping.

X-API-Key
Implementation details

Persists the mapping the merchant confirmed. Every target field is checked against the vocabulary for this import’s entityType, and an unknown one is rejected by name; two columns mapped to one field are rejected with both headers named. A source column the merchant chose to ignore is simply left out of `columns`. **This retracts any dry run**: the import returns to `status: "mapping"` and `dryRunAt` is cleared, because a dry run attests to a file AND a mapping — so a validate has to be re-run before this import can be committed. An import that has already written rows cannot be re-mapped at all (409 `rows_already_written`): resetting its status would leave those rows with no way to remove them.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.
content-typeapplication/jsonYesRequired when the request includes a JSON body.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Request body

FieldTypeRequiredDescription
columnsobjectYesSource header to target field id, e.g. { "Order ID": "order.externalId" }. Omit a header to ignore that column.
dateOrderstringYesHow to read ambiguous dates. Required, never guessed — 03/04/2026 is two different days under dmy and mdy.
Values: iso, dmy, mdy
emptyPaymentPolicystringYesWhat a blank payment-status cell means.
Values: paid, unpaid, unknown
guestSentinelsstring[]NoCustomer-id values that mean “guest checkout” rather than a real buyer. Defaults to ["0", ""].
Default: ["0", ""]
createMissingCustomersbooleanNoOrder imports only: whether an unmatched buyer email may create a customer. Defaults to true.
Default: true

Responses

200 The import, with the saved mapping.

Common errors

Status Code Cause Fix
400 unknown_target_field A value in `columns` is not a target field for this import’s entityType. The message names the offending field ids. Fields are entity-specific: `customer.email` is not valid on an order import.
400 duplicate_target_field Two source columns are mapped to the same target field. The message names the field and both headers. Keep one and leave the other out of `columns` to ignore it — the importer would otherwise take the first and silently discard the second.
409 import_running A worker is currently validating or committing this import. Wait for the run to finish before changing the rules it is running under.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
GET /api/v1/imports/{id} Server-side

Read one import, with progress.

X-API-Key
Implementation details

Progress is polled from the counters on the import row rather than streamed. `progress.percent` is null until the row total is known.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Responses

200 The import.

Response fields

FieldTypeRequiredDescription
statusstringNoLifecycle state.
Values: created, mapping, validating, validated, committing, completed, failed, undone
progressobjectNo{ rowsTotal, rowsProcessed, percent }. rowsTotal is established at the end of the validate pass — nothing can count a file’s rows without parsing it — so percent is null during validate and real for the commit that follows.
errorSummaryobjectNoFailure counts grouped by error code, with one sample message each.
lastPassModestring | nullNoWhich pass wrote the counters above — `validate` or `commit`, null before any run. **Read it before reading `rowsCreated`:** a dry run reports what it WOULD create in the same field, so `rowsCreated: 6` means six rows exist only when this is `commit`.
Values: validate, commit
undoResultobject | nullNoWhat the last undo removed and kept: { ordersDeleted, customersDeleted, ordersKept, customersKept, keptReasons }. Null until an undo has run.

Common errors

Status Code Cause Fix
403 environment_mismatch The import belongs to a different workspace environment than the credential used. Use a key (or dashboard environment) for the environment the import was created in.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/imports/{id}/validate Server-side

Queue a dry run.

X-API-Key
Implementation details

Reads the whole file under the saved mapping and writes nothing: the response reports what a commit would create, update, skip and reject. Requires a saved mapping. Answers 202 with the import row at its PRE-run status — the worker owns the transition to `validating`, so poll GET /api/v1/imports/{id} for the outcome. The dry run is also what establishes `rowsTotal`, so the commit that follows can report real progress.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Responses

202 The import row; the run is queued.

Common errors

Status Code Cause Fix
400 mapping_required The import has no saved mapping. PATCH /api/v1/imports/{id}/mapping with the confirmed column mapping first.
409 import_running A worker is already validating or committing this import. Wait for the run in flight to reach a terminal status.
409 already_committed The import has already completed a commit, or has been undone. Create a new import to load the file again.
409 rows_already_written A commit of this import has written rows. A dry run over them would replace the counters and status those rows are tracked by, leaving no way to remove them. POST /api/v1/imports/{id}/undo first, or create a new import.
503 queue_unavailable The deployment has no import worker queue configured. Nothing was started. Retry once the worker is available; an import never runs inside the API process.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/imports/{id}/commit Server-side

Queue the real import.

X-API-Key
Implementation details

Requires a passed dry run: `status: "validated"`, or `status: "failed"` when the failure came from a commit that had already been validated (`dryRunAt` set), in which case the run resumes from its checkpoint rather than restarting. A commit rewrites order and customer history from the file, so an import that has never passed a dry run — including one whose VALIDATE failed — is refused rather than run. Answers 202 with the import row at its pre-run status; poll GET /api/v1/imports/{id}.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Responses

202 The import row; the write pass is queued.

Common errors

Status Code Cause Fix
409 validation_required The import has never passed a dry run — any status other than `validated`, or a `failed` one with no `dryRunAt` (its validate is what failed). POST /api/v1/imports/{id}/validate and wait for status `validated`.
409 import_running A worker is already validating or committing this import. Wait for the run in flight to reach a terminal status.
409 already_committed This import has already committed, or has been undone. There is never a second write pass. Create a new import to load the file again.
503 queue_unavailable The deployment has no import worker queue configured. Nothing was started. Retry once the worker is available.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
POST /api/v1/imports/{id}/undo Server-side

Queue the deletion of the rows this import created.

X-API-Key
Implementation details

Only from a commit that actually ran: `status: "completed"` with a recorded `committedAt`, or `status: "failed"` **with `dryRunAt` set**, which is a commit that died partway and left rows behind. A dry run writes nothing, so there is nothing to undo (a `failed` import with no `dryRunAt` is a failed VALIDATE), and a run still in flight must finish first. Deletes only rows this import CREATED, never a row it merely matched and updated. A created row that something else now depends on (a payment, a fulfillment, a return, a cart, an invoice, a subscription or a quote) is KEPT, and `keptReasons` names the relation that blocked each one rather than reporting a generic "in use". **Runs on the queue**: answers 202, moves the import to `undoing`, and writes the outcome to `undoResult` on the import row — poll GET /api/v1/imports/{id} the same way you poll a validate or a commit. The import is marked `undone` only when nothing was kept, so one whose rows were blocked stays undoable once the blocker clears.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Responses

202 The import row, unchanged — the delete is queued, and the worker moves the import to `undoing` a moment later. Poll GET /api/v1/imports/{id} and read `undoResult`, which is null until the job has run. A commit that COMPLETES after an undo was requested cancels it: the queued undo finds its precondition gone and does nothing, rather than reverting an import the merchant has just been told succeeded.

Response fields

FieldTypeRequiredDescription
undoResultobject | nullNoNull at 202. Once the job has run: { ordersDeleted, customersDeleted, ordersKept, customersKept, keptReasons }. `keptReasons` is keyed by the relation that blocked the delete, e.g. { "payments": 2, "quotes": 1 }; each kept row is counted once, under the first relation that claimed it, so the totals reconcile with ordersKept + customersKept.
statusstringNo`undone` only when nothing was kept. If rows were blocked it returns to `completed`/`failed` and stays undoable, so the merchant can retry once the blocker clears.

Common errors

Status Code Cause Fix
409 undo_not_available This import has written nothing to remove — it was only validated (a dry run writes nothing, whatever its counters say), its VALIDATE is what failed (`failed` with no `dryRunAt`), its commit failed before writing a row (`rowsCreated: 0`, no `committedAt`), a run is still in flight, or it has already been fully undone. Only an import whose commit actually wrote rows — completed, or failed partway after a passing dry run — has anything to remove. An import that wrote nothing needs no undo: re-map it and run it again.
503 queue_unavailable The deployment has no import worker queue configured. Nothing was deleted. Retry once the worker is available; an undo never runs inside the API process.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
GET /api/v1/imports/{id}/errors Server-side

The row-level error report.

X-API-Key
Implementation details

Generated on request from the stored row errors. `csv` is re-importable: the original columns of each failed row, plus `_row_number` (the line number in YOUR file, so a corrected re-upload still points at the right row), `_error_code` and `_error_message`. Column order is not the file’s. **Read `rowsIncluded` against `rowsFailed` before treating the report as complete** — at most 10,000 row errors are retained per pass, so a run with 40,000 failures returns 10,000 of them and `truncated: true`. Fix those, re-import, and repeat.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Path parameters

FieldTypeRequiredDescription
iduuidYesImport id.

Responses

200 The report.

Response fields

FieldTypeRequiredDescription
rowsFailedintegerNoRows the run rejected in total, uncapped.
rowsIncludedintegerNoRows this report actually contains. Lower than rowsFailed when the retention cap was reached.
truncatedbooleanNorowsIncluded < rowsFailed.
csvstringNoThe re-importable CSV, as text.
filenamestringNoSuggested download name.
errorsarrayNoThe same rows structured for display: { rowNumber, columnName, code, message }.

Common errors

Status Code Cause Fix
404 no_errors The import recorded no failed rows (including one that has not run yet). Nothing to fix — the run rejected nothing.
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.
GET /api/v1/imports Server-side

List imports, newest first.

X-API-Key
Implementation details

Scoped to the application and workspace environment the credential resolves to. Cursor-paginated: pass the previous page’s `meta.pagination.cursor` back as `cursor`; a null cursor means there are no more pages.

Headers

FieldTypeRequiredDescription
x-api-keystringYesYour Throttle secret API key. Keep this on your backend only.

Query parameters

FieldTypeRequiredDescription
limitintegerNoPage size, 1 to 100.
Default: 25
cursoruuidNoThe id of the last import on the previous page.

Responses

200 A page of imports, newest first.

Common errors

Status Code Cause Fix
401 unauthorized Missing, malformed, or revoked API key. Send a valid secret key from your backend and rotate the key if it may have leaked.
422 validation_error A required field is missing or a field does not match the expected type, enum, or format. Compare the body against the field table. Amounts are integer minor units and UUID fields must be valid UUIDs.