---
title: "Paywall Checkout"
method: POST
path: "/v1/teams/{team_id}/billing/paywall/checkout"
tags: ["billing"]
---

# Paywall Checkout

`POST /v1/teams/{team_id}/billing/paywall/checkout`

## Request

- Base URL: `https://api.stormy.ai` (the document declares no server; this is the origin it was published from)
- URL: `https://api.stormy.ai/v1/teams/{team_id}/billing/paywall/checkout`
- Auth: none declared

Mint the checkout for a plan: Usage ($0 today, minutes and texts) by
default, or Production (`plan="production"`, `size` 300/600/900 — the base
is charged when the card goes on and comes back as prepaid usage).

TEAM ACCESS ONLY, DELIBERATELY — unlike `/test-checkout` above, which keeps
the billing-owner requirement because its caller CHOOSES the offer. Here
the offer is fixed server-side and the call mints an unpaid session: no
charge happens until a person enters a card into fields Stripe draws. The agent
draws this screen during onboarding with `role='agent'` / `['agent.run']`,
so requiring `billing.manage` would mean granting every harness worker the
capability to manage billing.

`concession` is accepted and changes nothing: there is no discount below
the plan, and a caller that asks for one gets the same plan.

## Path parameters

- `team_id` string, uuid, required

## Headers

- `authorization` string, nullable
- `x-darwin-token` string, nullable

## Request body

- PaywallCheckoutRequest: The paywall's own checkout. The offer is not the caller's to choose. `concession` is a boolean rather than a coupon id on purpose: the client asks for THE concession, and the server decides which one that is. A client that could name a coupon could name any coupon in the account. `embedded` is a boolean for the same reason and answers the owner's ask (2026-08-28) for the card field to be "on the page, not like on the page in bad of the card input". It is the SAME session against the same price and coupon — only where Stripe draws it changes — so it buys a client secret for `kit-ui`'s `Checkout` instead of a URL to hand somebody away to. It defaults to false, which is the hosted handover this route has always minted, so every caller that predates it is unaffected. `card_field` is the same boolean one step further, and it answers what the owner said when he saw what `embedded` actually drew (2026-08-28): "the checkout don't show the just have the payment method input of the credit card … You didn't have to do any of this. This actually breaks the flow." It asks for the CARD INPUT AND NOTHING ELSE — no order summary, no wallets, no save-my-information, no Stripe button — which Embedded Checkout cannot be configured into, so the server answers it with a SetupIntent against the same offer and the same coupon. A boolean for the third time for the third statement of one reason: the client asks for a KIND OF SURFACE, and what it costs and how it is arranged stays the server's.
  - `concession` boolean
  - `plan` 'usage' | 'production'
  - `size` '300' | '600' | '900', nullable
  - `embedded` boolean
  - `card_field` boolean

## Response `200`

Successful Response

- object

## Other responses

- `422`: Validation Error

## Changes

> 70 revisions in range; 1 not diffed.

- **2026-10-12** `93655953beee`: 2 info
  - added the new optional request property `plan`
  - added the new optional request property `size`

[Operation history](https://skmtc.dev/stormy/apis/stormy-control-plane/changes/v1/teams/:team_id/billing/paywall/checkout/post.md)

---

[API](https://skmtc.dev/stormy/apis/stormy-control-plane.md) · [All operations](https://skmtc.dev/stormy/apis/stormy-control-plane/llms.txt) · [OpenAPI document](https://skmtc.dev/stormy/apis/stormy-control-plane/revisions/4d811ab18158?raw)
