Skip to main content

Stripe-billing API

Auto-generated from the OpenAPI spec. Run node docs-site/scripts/fetch-openapi.js to regenerate.

GET /api/billing/settings​

Get Billing Settings

Return current Stripe connection status. Never returns the secret key.

Response includes webhook_configured: boolean — whether a real webhook signing secret is set (false for an empty value or the dev placeholder). The desktop app uses this to warn a manager who has recurring membership types but no webhook configured, since renewals/failed payments/cancellations are only ever learned about via webhook — unlike one-off purchase activation, which also has a reconcile-on-return safety net (see POST /checkout-session/{id}/reconcile below).

Responses

  • 200 — Successful Response

POST /api/billing/settings​

Post Billing Settings

Persists Stripe credentials. Supports two independent request shapes, matching the two separate "Save" actions in the desktop app's Billing tab — one for the always-visible webhook secret field, one for the manual API keys inside "Advanced options".

Request body — provide secret_key (+ publishable_key), or webhook_secret alone, or both.

FieldRequiredTypeDescription
secret_keyNostringValidates against Stripe and switches on the full key-setup path below. Requires publishable_key alongside it.
publishable_keyNostringRequired if, and only if, secret_key is provided.
webhook_secretNostringProvided alone (no secret_key): updates only the webhook signing secret, no Stripe API call, no change to the studio's connection status. Provided alongside secret_key: a manual override for advanced/self-managed setups, skipping the automatic registration below.

At least one of secret_key or webhook_secret must be provided.

Full key-setup path (secret_key present): validates the key against Stripe, persists both keys, and — unless webhook_secret was also given as a manual override — automatically registers a Stripe webhook endpoint against the studio's own tunnel URL, persisting the signing secret it gets back (no manual Stripe Dashboard step needed in this path). Safe to call repeatedly ("Test connection"): an existing endpoint for the same URL is reused/updated in place, never duplicated. Response includes a webhook_endpoint: {configured, created, url} block describing the registered endpoint when this path runs.

Webhook-only path (webhook_secret present, secret_key absent): persists only the webhook secret. No Stripe API call is made, and stripe_connected/stripe_account_id are left untouched — this is for a studio that's already connected (via either path) and just needs to set or replace its webhook secret, e.g. after following the Stripe webhook setup guide for a studio connected via Connect with Stripe, which can't auto-register a webhook itself (Stripe restricts OAuth-issued tokens from managing webhook endpoints).

Responses

  • 200 — Successful Response
  • 400 STRIPE_SETTINGS_EMPTY — Neither secret_key nor webhook_secret was provided.
  • 400 STRIPE_TUNNEL_NOT_CONFIGURED — No tunnel URL is configured yet, so there's nothing to register the webhook endpoint against (full key-setup path only). Configure the studio's Cloudflare Tunnel first (Agon Settings → Connectivity).
  • 502 STRIPE_WEBHOOK_SETUP_FAILED — The Stripe API call to list/create/update the webhook endpoint failed (e.g. an invalid key, rejected by Stripe). The secret key is never echoed back in this error.
  • 422 STRIPE_PUBLISHABLE_KEY_REQUIRED — secret_key was provided without publishable_key.
  • 422 — Validation Error

GET /api/billing/connect/authorize​

Start "Connect with Stripe" — a second, easier onboarding path for a non-technical manager, alongside the manual secret-key form above. Manager-only (JWT).

Returns the URL to open in the manager's own OS browser to begin the Stripe Connect OAuth flow. Agon never asks the manager for their Stripe secret key on this path — Stripe handles authentication and account selection on its own hosted page.

Responses

  • 200 — {"authorize_url": "https://connect.stripe.com/oauth/authorize?..."}
  • 400 STRIPE_CONNECT_NOT_CONFIGURED — the studio hasn't finished initial setup yet (no public_studio_id/directory_secret). Finish Agon Settings setup first, or use the manual secret-key form instead.

After connecting this way, the manager still needs to create a webhook endpoint manually in their own Stripe Dashboard — Stripe doesn't let an OAuth-connected account register one automatically. See the studio-manager guide: Stripe Webhook Setup.

Why this needs a relay: Stripe Connect's OAuth token exchange requires Agon's own platform secret key, which can never be shipped inside a self-hosted Agon install. That one step is delegated to a narrow-scope Cloudflare Worker relay (see directory-worker/CLAUDE.md §9.3 in the Agon repo) that holds the platform secret and nothing else — it never sees a studio's own Stripe key, a card, a charge, or any client data, only authorization code → Stripe account id.

GET /api/billing/connect/return​

Not a JSON API endpoint — this is where the relay redirects the manager's own browser after Stripe authorizes the connection. It renders a small static "connected" or "try again" HTML page directly (never a redirect back into the Agon desktop app, since there's no reliable deep-link back into Electron from an arbitrary OS browser). The Agon desktop app itself learns the result by re-polling GET /api/billing/settings (on window focus, or via a manual "Refresh status" button) — not by any callback from this page.


POST /api/billing/checkout-session​

Create Checkout Session

Create a Stripe Checkout Session for a membership purchase.

Supports both one-off (mode="payment") and recurring (mode="subscription") purchases depending on the membership type.

Accessible by authenticated clients (who may only purchase for themselves) and by managers (who may initiate on behalf of any client).

Request body

FieldRequiredTypeDescription
client_idYesinteger
membership_type_idYesinteger
success_urlYesstring
cancel_urlYesstring
promo_codeNostring
gift_card_codeNostring

Responses

  • 200 — Successful Response
  • 422 — Validation Error

POST /api/billing/checkout-session/{stripe_session_id}/reconcile​

Reconcile Checkout Session

Safety net for a checkout whose webhook never arrived: lets the client (or staff) ask Stripe directly whether a specific Checkout Session actually completed, and activates the membership if so. Webhook delivery is the primary mechanism and can silently stop working; a studio connected via Stripe Connect OAuth may also have no webhook configured at all until one is set up manually (see GET /connect/authorize above). Safe to call even if the webhook already handled it — idempotent, never creates a duplicate membership regardless of call order or timing.

Parameters

NameInRequiredTypeDescription
stripe_session_idpathYesstringThe Stripe Checkout Session id, returned by POST /api/billing/checkout-session.

Responses

  • 200 — {"status": "complete" | "open" | "expired", "reconciled": boolean}. reconciled: true only when this call actually activated a membership that wasn't already active; false means either it was already complete (webhook got there first) or Stripe reports it's still unpaid.
  • 404 CHECKOUT_SESSION_NOT_FOUND — unknown session id, or it belongs to a different client than the caller (deliberately the same generic error for both cases — never confirms to the wrong caller that a given session id exists).
  • 502 STRIPE_API_ERROR — the live Stripe lookup failed.
  • 503 STRIPE_NOT_CONFIGURED — no Stripe secret key configured for this studio yet.

POST /api/billing/webhook​

Stripe Webhook

Stripe webhook receiver. No auth — Stripe signs the payload.

Responses

  • 200 — Successful Response

GET /api/billing/members/{client_id}/subscription​

Get Subscription Status

Return the most recent Stripe subscription for a client.

Accessible by managers, instructors, or the client themselves.

Parameters

NameInRequiredTypeDescription
client_idpathYesinteger

Responses

  • 200 — Successful Response
  • 422 — Validation Error

POST /api/billing/members/{client_id}/subscription/cancel​

Cancel Subscription

Cancel a client's active subscription at period end. Manager-only.

Parameters

NameInRequiredTypeDescription
client_idpathYesinteger

Responses

  • 200 — Successful Response
  • 422 — Validation Error

POST /api/billing/members/{client_id}/subscription/cancel/override​

Cancel Subscription Override

Cancel a client's subscription locally in the DB without calling Stripe.

Safety valve for when Stripe is unreachable or webhooks fail. Manager-only. Does NOT make any network calls to Stripe.

Parameters

NameInRequiredTypeDescription
client_idpathYesinteger

Responses

  • 200 — Successful Response
  • 422 — Validation Error