Flows
Checkout & accounts
Sell prop-trading challenges with Stripe. Create a PaymentIntent, collect payment with Stripe Elements, and a funded prop account is provisioned automatically when the payment succeeds.
How it works
- Call
POST /v2/payments/checkoutto create a PaymentIntent. - Confirm payment in the browser with the returned
client_secret+ Stripe Elements. - Stripe fires
payment_intent.succeeded→ the API provisions a prop account. - Poll
GET /v2/payments/prop-accountsuntil the new account appears.
Provisioning is async
payment.succeeded webhook before redirecting the user.Create a checkout (PaymentIntent)
/v2/payments/checkouttier_id | string | required | Challenge tier identifier |
market | string | required | e.g. "crypto" |
asset_class | string | required | e.g. "crypto" / "forex" |
account_size | number | required | Funded account size in USD |
amount_cents | number | required | Price charged, in cents |
currency | string | optional | Defaults to "usd" |
curl -X POST http://localhost:8000/v2/payments/checkout \
-H "Authorization: Bearer <app_access_token>" \
-H "X-Session-Token: <user_session_token>" \
-H "Content-Type: application/json" \
-d '{
"tier_id": "tier_25k",
"market": "crypto",
"asset_class": "crypto",
"account_size": 25000,
"amount_cents": 19900,
"currency": "usd"
}'{
"payment_id": "pay_...",
"stripe_payment_intent_id": "pi_...",
"client_secret": "pi_..._secret_...",
"amount_cents": 19900,
"currency": "usd",
"status": "requires_payment_method"
}import { loadStripe } from "@stripe/stripe-js";
const stripe = await loadStripe(process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!);
// render <Elements options={{ clientSecret }}> with <PaymentElement />, then:
await stripe.confirmPayment({ elements, redirect: "if_required" });Create a free account
Provision a no-charge account (e.g. for demos or comped traders) without Stripe.
/v2/payments/free{ "tier_id": "tier_demo", "asset_class": "crypto", "account_size": 10000 }{
"id": "prop_...",
"status": "evaluation",
"subaccount_id": 42,
"subaccount_uuid": "9c1...",
"synthetic_hotkey": "5F..."
}{
"id": "prop_...",
"status": "subaccount_failed",
"subaccount_id": null,
"subaccount_uuid": null,
"synthetic_hotkey": null
}200 does not mean provisioned
200 with a prop-account body in both the success and the failure case, so you must branch on the response status rather than on the HTTP code. A subaccount_failed account has subaccount_id, subaccount_uuid and synthetic_hotkey set to null and can never trade — every /v2/trading/* call, reads included, returns 409 V2_SUBACCOUNT_NOT_READY, because the read routes resolve the prop account through the same guard as the writes. The API answers 200 rather than 5xx on purpose: raising would roll back the request session and destroy the row that records the attempt. Do not auto-retry — surface the account for operator recovery via GET /v2/payments/prop-accounts/failed.List prop accounts
status is one of: provisioning (subaccount create in flight), evaluation (provisioned and tradeable), funded, eliminated (both set by POST /v2/lifecycle/sync/{prop_account_id}), and subaccount_failed (charged but not provisioned). It is never “active” — gate your trading entry point on evaluation or funded.
/v2/payments/prop-accounts[
{
"id": "prop_...",
"tier_id": "tier_25k",
"asset_class": "crypto",
"account_size": 25000,
"status": "evaluation",
"subaccount_id": 42,
"subaccount_uuid": "9c1...",
"synthetic_hotkey": "5F...",
"stripe_payment_intent_id": "pi_..."
}
]/v2/payments/prop-accountsRuns live against your environment using the app's server-side credentials and your session. Sign in to the dashboard first for authenticated reads.
Fetch one by id with GET /v2/payments/prop-accounts/{id}.