Endpoint reference
Every route the Offer API serves, grouped by resource. Paths are relative to the API's base URL, http://localhost:6767 when you run it locally.
Under each route is the credential it takes. The admin key also works on every /apps/:appId route. Authentication explains each credential and the error format. To send these requests, import the Postman collection: it has every route below with an example body.
| Label | Meaning |
|---|---|
| Secret key | The app's key_… key, from your server. |
| Secret or publishable key | The pub_… key works too, so the route can be called from a browser. |
| …or the account’s own token | An act_… token works, for its own account only. |
| Admin key only | The API's ADMIN_API_KEY. App keys get 401. |
| No credential | Open to anyone. |
Ids you choose for plans, entitlements, add-ons, incentives, offers and cancel flows are normalized: lowercased, spaces turned into underscores, and anything other than a-z, 0-9 and _ removed. Use underscores in ids, since hyphens are dropped. Account ids are kept as you send them. Most PATCH routes merge the fields you send into the record, and none of them change its id.
Service
- GET
/Plain-text banner, to check the service is up. - GET
/healthRuns a query against Postgres and returns { ok: true }. Render's health check. - GET
/openapi.jsonThe OpenAPI 3.1 document. Covers the core routes only. - GET
/docsInteractive API reference rendered from /openapi.json. - GET
/event-typesThe webhook event catalog, with a sample data payload for each type.
Apps
An app is one product you sell. Creating one returns its secret and publishable keys.
- POST
/appsCreates an app. Body: { name, plan? }. Returns the app with api_key and public_key. - GET
/apps/:appIdThe app record, including its keys and checkout_url. - PATCH
/apps/:appIdMerges fields into the app, such as name or checkout_url. Key fields are ignored. - DELETE
/apps/:appIdDeletes the app and every plan, entitlement, add-on, incentive, account and counter in it. - POST
/apps/:appId/keys/regenerateReplaces the secret key. Also revokes every account token. - POST
/apps/:appId/public-key/regenerateReplaces the publishable key.
Accounts
Accounts are your customers, keyed by an id you choose. In paths they are called namespaces.
- GET
/apps/:appId/namespacesSearches accounts. Query: q, plan, incentive, offer, has_incentive, page, per_page. - GET
/apps/:appId/namespaces/countNumber of accounts in the app. - GET
/apps/:appId/namespaces/with-incentiveAccounts that have an incentive. Query: incentive, page, per_page. - POST
/apps/:appId/namespacesCreates an account. Body: { id, name?, plan, incentive? }. 409 if the id exists. - GET
/apps/:appId/namespaces/:accountIdThe account record, with its subscription and own add-ons. - PATCH
/apps/:appId/namespaces/:accountIdMerges fields into the account: name, plan, incentive, incentive_expires_at. Changes the plan without payment. - DELETE
/apps/:appId/namespaces/:accountIdDeletes the account and its usage counters. Usage history stays for analytics. - DELETE
/apps/:appId/namespaces/:accountId/incentiveRemoves the account's incentive. - POST
/apps/:appId/namespaces/:accountId/tokenMints an account token (act_…). Body: { ttl_seconds? }, 60 to 86,400. - GET
/apps/:appId/namespaces/:accountId/planEffective access: plan, offer extras and incentive merged, with usage, max, left and can per entitlement. Private meta removed. - GET
/apps/:appId/namespaces/:accountId/full-planSame as /plan, with private meta included. - POST
/apps/:appId/namespaces/:accountId/addonsGives the account an add-on of its own. Body: { id }. - DELETE
/apps/:appId/namespaces/:accountId/addons/:addonIdRemoves an add-on the account owns.
Usage
Counters for usage entitlements. Calls on a boolean entitlement return 400.
- GET
/apps/:appId/namespaces/:accountId/usageCounts for every entitlement on the account's plan, as { [entitlementId]: count }. - GET
/apps/:appId/namespaces/:accountId/usage/:entitlementIdOne counter: { entitlement, count }. - POST
/apps/:appId/namespaces/:accountId/usage/:entitlementId/addAdds 1. Returns 402 with an upgrade offer when a blocking limit would be passed. - POST
/apps/:appId/namespaces/:accountId/usage/:entitlementId/removeSubtracts 1. - POST
/apps/:appId/namespaces/:accountId/usage/:entitlementId/amountAdds { amount } (an integer, negative to subtract). Positive amounts can return 402. A negative amount with the publishable key returns 403.
Plans
- GET
/apps/:appId/plansEvery plan, in creation order. - GET
/apps/:appId/plans/pricingPricing cards of the plans that have one, for a pricing table. - POST
/apps/:appId/plansCreates a plan. Body: { id, name, description?, note?, isFree?, pricingCard? }. - GET
/apps/:appId/plans/:planIdOne plan. - PATCH
/apps/:appId/plans/:planIdMerges fields into the plan, including privateMetaKeys. - DELETE
/apps/:appId/plans/:planIdDeletes the plan. - GET
/apps/:appId/plans/:planId/pricingOne plan's pricing card. 404 when it has none. - PATCH
/apps/:appId/plans/:planId/metaMerges keys into the plan's meta. - GET
/apps/:appId/plans/:planId/namespacesAccounts on the plan. Query: page, per_page. - POST
/apps/:appId/plans/:planId/entitlementsAttaches an entitlement. Body: { id, max? }. max: null means unlimited. - PATCH
/apps/:appId/plans/:planId/entitlements/:entitlementIdChanges an attached entitlement's limit. Body: { max }. - DELETE
/apps/:appId/plans/:planId/entitlements/:entitlementIdDetaches an entitlement. - POST
/apps/:appId/plans/:planId/addonsAttaches an add-on. Body: { id }. - DELETE
/apps/:appId/plans/:planId/addons/:addonIdDetaches an add-on. - POST
/apps/:appId/plans/:planId/checkoutStarts a PayPal checkout for the plan at its regular price. Body: { interval?, account? | email?, ref?, return_url?, cancel_url? }.
Entitlements
- GET
/apps/:appId/entitlementsEvery entitlement. - POST
/apps/:appId/entitlementsCreates an entitlement. Body: { id, type: usage | boolean, name, description? }. - GET
/apps/:appId/entitlements/:entitlementIdOne entitlement. - PATCH
/apps/:appId/entitlements/:entitlementIdUpdates it. overage: { mode: allow | block, offer_id? } makes the API refuse usage past the limit with a 402. - DELETE
/apps/:appId/entitlements/:entitlementIdDeletes the entitlement.
Add-ons
- GET
/apps/:appId/addonsEvery add-on. - POST
/apps/:appId/addonsCreates an add-on. Body: { id, name, description? }. - GET
/apps/:appId/addons/:addonIdOne add-on. - PATCH
/apps/:appId/addons/:addonIdUpdates the add-on. - DELETE
/apps/:appId/addons/:addonIdDeletes the add-on.
Incentives
An incentive overrides limits and adds add-ons on top of an account's plan, such as a promo code or a partner deal.
- GET
/apps/:appId/incentivesEvery incentive. - POST
/apps/:appId/incentivesCreates an incentive. Body: { id, name, description?, entitlements?, addons? }. - GET
/apps/:appId/incentives/:incentiveIdOne incentive. - PATCH
/apps/:appId/incentives/:incentiveIdUpdates the incentive. - DELETE
/apps/:appId/incentives/:incentiveIdDeletes the incentive. - POST
/apps/:appId/incentives/:incentiveId/entitlementsAdds an entitlement override. Body: { id, max? }. - PATCH
/apps/:appId/incentives/:incentiveId/entitlements/:entitlementIdChanges an override's limit. Body: { max }. - DELETE
/apps/:appId/incentives/:incentiveId/entitlements/:entitlementIdRemoves an override. - POST
/apps/:appId/incentives/:incentiveId/addonsAdds an add-on. Body: { id }. - DELETE
/apps/:appId/incentives/:incentiveId/addons/:addonIdRemoves an add-on.
Offers
Offers sell plans at a price, with extras and order bumps. Publishing needs PayPal connected.
- GET
/apps/:appId/offersEvery offer, with live status and completed redemptions. - POST
/apps/:appId/offersCreates a draft offer. Shareable offers need an id (their slug); targeted offers take an account_id and get an unguessable id. - GET
/apps/:appId/offers/:offerIdOne offer as the dashboard sees it. - PATCH
/apps/:appId/offers/:offerIdEdits the offer. Applies to future purchases only. - DELETE
/apps/:appId/offers/:offerIdDeletes the offer. Buyers keep their price and extras. - POST
/apps/:appId/offers/draft-previewValidates unsaved offer fields and returns the public view a checkout page would show. Stores nothing. - POST
/apps/:appId/offers/:offerId/publishCreates the PayPal billing plans and makes the offer active. - POST
/apps/:appId/offers/:offerId/archiveStops new purchases. Existing buyers keep their terms. - POST
/apps/:appId/offers/:offerId/previewWhat each plan in the offer grants. Body: { account? } adds what would change for that account. - GET
/apps/:appId/offers/:offerId/statsViews, checkouts, conversion and revenue, by plan, by ref and by bump. - GET
/apps/:appId/offers/:offerId/publicThe offer a checkout page should show, falling back to fallback, then regular prices. Query: fallback, account, ref. Never 404. - POST
/apps/:appId/offers/:offerId/checkoutPrices the selection on the server and creates the PayPal subscription or order. Body: { plan, interval, bumps?, account? | email?, ref?, return_url?, cancel_url? }.
Checkouts
- GET
/apps/:appId/checkoutsRecent checkouts, newest first. Query: offer, status, limit. - GET
/apps/:appId/checkouts/:checkoutIdOne checkout. - POST
/apps/:appId/checkouts/:checkoutId/completeChecks the payment with PayPal and applies it. Returns status created while PayPal is still activating; call again shortly.
Subscriptions
An account's paid PayPal subscription. Changes need the customer's approval on PayPal and apply once PayPal reports them.
- GET
/apps/:appId/namespaces/:accountId/subscriptionThe subscription: offer, plan, price, renewal date and status. 404 when there is none. - POST
/apps/:appId/namespaces/:accountId/subscription/changeMoves to another plan or interval. Body: { plan, interval?, return_url?, cancel_url? }. Returns PayPal's approval link. - POST
/apps/:appId/namespaces/:accountId/subscription/syncRe-reads the subscription from PayPal. Webhooks do this automatically. - POST
/apps/:appId/namespaces/:accountId/subscription/cancelCancels in PayPal and moves the account to the free plan. Body: { reason? }. - POST
/apps/:appId/namespaces/:accountId/subscription/pauseSuspends billing for 1 to 12 months. Body: { months, reason? }. - POST
/apps/:appId/namespaces/:accountId/subscription/resumeEnds a pause now: billing restarts and the plan comes back.
Cancel flows
The steps, questions and save offers a customer sees when they cancel.
- GET
/apps/:appId/cancel-flowsEvery flow, with sessions and saves in the last 30 days. - GET
/apps/:appId/cancel-flows/capabilitiesWhether dynamic offers (AI) and workflow pauses (Render) are configured on this API. - POST
/apps/:appId/cancel-flowsCreates a flow. Body: { id?, name?, status?, steps? }. Without steps, starts from a template. - GET
/apps/:appId/cancel-flows/:flowIdOne flow. - PATCH
/apps/:appId/cancel-flows/:flowIdUpdates name, status (active or draft) or steps. steps replaces the whole list. - DELETE
/apps/:appId/cancel-flows/:flowIdDeletes the flow. - GET
/apps/:appId/cancel-flows/:flowId/statsSave rate, revenue kept, reasons and offer results. Query: days. - POST
/apps/:appId/cancel-flows/:flowId/preview-offerThe offer a customer would get for a set of answers, without starting a session.
Cancel sessions
One customer going through a flow. The SDK calls these with an account token; every call returns the session view.
- POST
/apps/:appId/cancel-sessionsStarts a session. Body: { flow?, account? }. With a token, the account comes from the token. - GET
/apps/:appId/cancel-sessionsRecent sessions with answers and offers. Query: flow, account, status, limit. - GET
/apps/:appId/cancel-sessions/:sessionIdOne session. - POST
/apps/:appId/cancel-sessions/:sessionId/answerAnswers the current step. Body: { step, answer?, text? }. - POST
/apps/:appId/cancel-sessions/:sessionId/backGoes back one step. - POST
/apps/:appId/cancel-sessions/:sessionId/declineTurns down the save offer. - POST
/apps/:appId/cancel-sessions/:sessionId/acceptApplies the save offer. Discounts and downgrades return approve_url. Body: { return_url?, cancel_url? }. - POST
/apps/:appId/cancel-sessions/:sessionId/cancelCancels the subscription from the confirm step. - POST
/apps/:appId/cancel-sessions/:sessionId/closeMarks an open session abandoned.
Analytics
Aggregates of recorded usage. interval is one of 7d, 30d, 60d, 6m, year or alltime.
- GET
/apps/:appId/analyticsCalls and total amount per entitlement. Query: interval (required), namespace. - GET
/apps/:appId/analytics/top-namespacesMost active accounts. Query: interval, limit, entitlement. - GET
/apps/:appId/analytics/timeseriesDaily buckets up to 60 days, weekly beyond. Query: interval, namespace, entitlement. - GET
/apps/:appId/analytics/eventsRecent usage events, newest first. Query: namespace, entitlement, limit. - GET
/apps/:appId/analytics/reportsSaved reports. - POST
/apps/:appId/analytics/reportsSaves a report. Body: { name, entitlements?, interval? }. - GET
/apps/:appId/analytics/reports/:idOne saved report. - PATCH
/apps/:appId/analytics/reports/:idUpdates a saved report. - DELETE
/apps/:appId/analytics/reports/:idDeletes a saved report.
Webhooks and events
Endpoints that receive signed events, their delivery logs, and the app's event log.
- GET
/apps/:appId/webhooksEvery endpoint, with delivery stats for the last 7 days. - POST
/apps/:appId/webhooksAdds an endpoint. Body: { url, events, description?, source?, enabled? }. Up to 25 per app. - GET
/apps/:appId/webhooks/:webhookIdOne endpoint, with its secret. - PATCH
/apps/:appId/webhooks/:webhookIdUpdates url, events, description or enabled. Re-enabling clears disabled_reason. - DELETE
/apps/:appId/webhooks/:webhookIdDeletes the endpoint and its deliveries. - POST
/apps/:appId/webhooks/:webhookId/secret/regenerateReplaces the signing secret. - POST
/apps/:appId/webhooks/:webhookId/testSends a sample event once and returns the delivery. Body: { type? }. - GET
/apps/:appId/webhooks/:webhookId/deliveriesDelivery log, newest first. Query: status, limit. - POST
/apps/:appId/webhooks/:webhookId/deliveries/:deliveryId/retrySends a delivery again, once, and returns the result. - GET
/apps/:appId/eventsEvents from the last 30 days, newest first. Query: type, limit.
PayPal
Each app connects its own PayPal REST app, sandbox or live.
- GET
/apps/:appId/paypalWhether PayPal is connected, the environment, client id and webhook registration. - PUT
/apps/:appId/paypalConnects PayPal. Body: { client_id, client_secret, env: sandbox | live }. Checks the credentials and registers the webhook when the API is public. - DELETE
/apps/:appId/paypalDisconnects PayPal. Existing subscriptions keep billing. - POST
/paypal/webhooks/:appIdPayPal's notifications for the app, mapped onto checkouts and accounts.
MCP server
Each app's MCP server, and the OAuth 2.1 routes MCP clients use to connect without a key. The MCP server docs page covers the protocol and the tools.
- POST
/apps/:appId/mcpThe app's MCP server: one JSON-RPC message (or a batch) per request, answered with JSON. 401 points to the resource metadata; 403 when the server is turned off. - GET
/apps/:appId/mcp405. The server is stateless and never streams to the client. - DELETE
/apps/:appId/mcp405. There is no session to end. - GET
/.well-known/oauth-protected-resource/apps/:appId/mcpProtected resource metadata (RFC 9728): the server URL, its authorization server and scopes. - GET
/.well-known/oauth-protected-resourceThe same, for clients that drop the path. The resource is the API's origin. - GET
/.well-known/oauth-authorization-serverAuthorization server metadata (RFC 8414). Also served under /.well-known/oauth-authorization-server/* and /.well-known/openid-configuration. - POST
/oauth/registerDynamic client registration (RFC 7591). Body: { redirect_uris, client_name?, token_endpoint_auth_method?, client_uri?, logo_uri? }. - GET
/oauth/authorizeStarts an authorization code request with PKCE (S256) and redirects to the dashboard's consent page. - POST
/oauth/tokengrant_type authorization_code (with code_verifier) or refresh_token. Refresh tokens rotate on every use. - POST
/oauth/revokeRevokes an access or refresh token (RFC 7009), which ends the connection.
Admin only
Routes the dashboard and the Render Workflow call with the admin key. App keys get 401.
- GET
/orgsEvery dashboard workspace. - POST
/orgsCreates a workspace. Body: { id, app_ids? }. - GET
/orgs/:orgIdOne workspace. - PATCH
/orgs/:orgIdUpdates app_ids, the apps the workspace shows. - DELETE
/orgs/:orgIdDeletes the workspace record. Its apps stay. - GET
/orgs/:orgId/appsThe workspace's apps, in app_ids order. - GET
/pauses/duePauses due to resume. Query: limit. - POST
/pauses/applyApplies a pause. Body: { app_id, account_id, months, session_id?, reason? }. - POST
/pauses/resumeResumes a paused subscription. Body: { app_id, account_id }. - POST
/apps/:appId/importImports history: backdated accounts and usage events, up to 10,000 of each. No webhooks fire. - GET
/apps/:appId/agent/threadsThe dashboard agent's chat threads for a user. Query: user_id, limit. - GET
/apps/:appId/agent/threads/:threadIdOne thread, with messages. - PUT
/apps/:appId/agent/threads/:threadIdCreates a thread or replaces its messages. Body: { user_id, messages, title? }. - PATCH
/apps/:appId/agent/threads/:threadIdRenames a thread. Body: { title }. - DELETE
/apps/:appId/agent/threads/:threadIdDeletes a thread. - GET
/apps/:appId/mcp/settingsThe server's settings: enabled, access_level and tool_overrides. - PATCH
/apps/:appId/mcp/settingsUpdates enabled, access_level (read, write or full) or tool_overrides, a map of tool name to true or false that replaces the old one. - GET
/apps/:appId/mcp/connectionsConnected clients, most recently used first, with tool calls in the last 7 days. - DELETE
/apps/:appId/mcp/connections/:connectionIdRemoves a connection. An OAuth connection loses access on its next call. - GET
/apps/:appId/mcp/callsTool calls, newest first, kept 30 days. Query: connection_id, limit. - GET
/oauth/requests/:requestIdA pending authorization request, for the consent page. 404 once used or expired. - POST
/oauth/requests/:requestId/approveApproves a request. Body: { user: { id, name?, email? }, access_level, app_id? }. Returns redirect_url with the code. - POST
/oauth/requests/:requestId/denyDenies a request. Returns redirect_url with error=access_denied. - GET
/api/auth/*Dashboard sign-in (better-auth), proxied by the dashboard. - POST
/api/auth/*Dashboard sign-in (better-auth), proxied by the dashboard.