You're on the production developer portal. The interactive demo — signup, Sumsub KYC, the purchase flow, webhooks, and the trading dashboard — is disabled here because it would create real accounts. Test those flows in the sandbox against the staging API.

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

  1. Call POST /v2/payments/checkout to create a PaymentIntent.
  2. Confirm payment in the browser with the returned client_secret + Stripe Elements.
  3. Stripe fires payment_intent.succeeded → the API provisions a prop account.
  4. Poll GET /v2/payments/prop-accounts until the new account appears.

Provisioning is async

The account is created by the webhook, not the checkout call. Poll (this app waits up to ~30s) or listen for the payment.succeeded webhook before redirecting the user.

Create a checkout (PaymentIntent)

POST/v2/payments/checkout
User session
Request body
tier_idstring
required
Challenge tier identifier
marketstring
required
e.g. "crypto"
asset_classstring
required
e.g. "crypto" / "forex"
account_sizenumber
required
Funded account size in USD
amount_centsnumber
required
Price charged, in cents
currencystringoptionalDefaults to "usd"
curl
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"
  }'
200 OK
{
  "payment_id": "pay_...",
  "stripe_payment_intent_id": "pi_...",
  "client_secret": "pi_..._secret_...",
  "amount_cents": 19900,
  "currency": "usd",
  "status": "requires_payment_method"
}
confirm in the browser
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.

POST/v2/payments/free
User session
Request body
{ "tier_id": "tier_demo", "asset_class": "crypto", "account_size": 10000 }
200 OK — provisioned
{
  "id": "prop_...",
  "status": "evaluation",
  "subaccount_id": 42,
  "subaccount_uuid": "9c1...",
  "synthetic_hotkey": "5F..."
}
200 OK — NOT provisioned
{
  "id": "prop_...",
  "status": "subaccount_failed",
  "subaccount_id": null,
  "subaccount_uuid": null,
  "synthetic_hotkey": null
}

200 does not mean provisioned

This endpoint returns 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.

GET/v2/payments/prop-accounts
User session
200 OK
[
  {
    "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_..."
  }
]
GET
/v2/payments/prop-accounts Try it in the sandbox

Fetch one by id with GET /v2/payments/prop-accounts/{id}.