Skip to content
/api/v1/payments/create-intent

Create a Stripe Payment Intent for a one-time top-up of the USD `credit_balance` wallet.

Create a Stripe Payment Intent for a one-time top-up of the USD `credit_balance` wallet. EXEMPT (`cost: 0`). Returns the client secret — pass to Stripe Elements client-side to collect card details. Minimum top-up: $5.

Why use this

Server-side wrapper for `stripe.PaymentIntent.create` — initialises a one-time top-up of the user's USD credit balance (the `users.credit_balance` wallet, NOT the token ledger). EXEMPT (`cost: 0`). Returns a Stripe `client_secret` which the frontend passes to Stripe Elements (`stripe.confirmCardPayment(clientSecret)`) to collect card details client-side without the card data ever touching our servers (PCI scope-out). On successful charge, Stripe webhooks `payment_intent.succeeded` to /api/v1/payments/webhook, which calls `_fulfill_topup` to credit the wallet. Use [POST /api/v1/payments/verify](/docs/account/payments-module/post-payments-verify) as a fallback when webhook delivery is delayed/lost. NB: this endpoint creates a Stripe Customer record on first use (idempotent — `stripe_customer_id` is cached on the user row).

Common use case

Frontend 'Top up balance' modal: user enters $25 → POST here → receives `client_secret` → Stripe Elements form mounted with the secret → user enters card → `confirmCardPayment` fires → Stripe processes → webhook fulfils → modal polls /api/v1/account/balance to show updated balance.

Creates a Stripe Payment Intent for a one-time USD top-up of the user's credit_balance wallet. EXEMPT (cost: 0). The wallet is the dollar-denominated buffer used by POST /api/v1/billing/quota to purchase additional API request quota at 100 requests / $1; it is NOT the token-balance ledger (those are entirely separate — see GET /api/v1/account/balance). The endpoint is a thin wrapper around stripe.PaymentIntent.create, returning the client_secret for Stripe Elements to consume client-side (PCI scope-out — card data never traverses our servers). On successful charge Stripe webhooks payment_intent.succeeded to /api/v1/payments/webhook, which calls _fulfill_topup to credit the wallet and write a Transaction row of type TOPUP. If the webhook is delayed/lost, the frontend can fall back to POST /api/v1/payments/verify to manually retrieve the intent status from Stripe and trigger fulfilment. Customer-facing subscription management (cards on file, cancellations) is handled separately via POST /api/v1/account/billing-portal.

Parameters

NameInRequiredDefaultAllowedDescriptionExample
amountbodyrequiredTop-up amount in USD (NOT in cents — server multiplies by 100 internally to send `amount_cents` to Stripe). Minimum 5; below 5 returns 400 with `Minimum top-up is $5`. Float accepted (e.g. 12.50) — fractional cents are rounded by `int(amount_dollars * 100)`.29

Response schema

FieldTypeNullableDescription
statusstringnoApiResponse envelope status — `success` on 200, `error` on 4xx/5xx. Always present in the standardised `ApiResponse` wrapper used across `/api/v1/*`.
request_idstringyesPer-request correlation ID (set by Flask `g.request_id`). Pass in support tickets to look up the call in server logs.
timestampstringnoISO-8601 UTC timestamp the response was generated.
dataobjectnoPayload object containing the Stripe client secret.
data.clientSecretstringnoStripe Payment Intent client secret in `pi_XXXXXXX_secret_YYYYYYY` format. Pass to `stripe.confirmCardPayment(clientSecret)` client-side via Stripe Elements. Single-use — once confirmed (success or failure) it cannot be re-used; create a new intent for a retry. Embeds the user's `uuid` and `type=TOPUP` in Stripe-side metadata so the webhook handler (`_fulfill_topup`) can credit the correct wallet on success.

Sample response

·
  • "status": "success"
  • "request_id": "req_3OqK2jK9L8pQ4xZ1"
  • "timestamp": "2026-05-02T15:51:00.000Z"
  • "data":
    • "clientSecret": "pi_3OqK2jK9L8pQ4xZ1_secret_xVZq8nL7aP2kT6sB1mDfHj9vC"
    }
}

Errors

StatusLabelDescription
200OKRequest succeeded.
400Bad RequestInvalid query, body, or path parameter.
401UnauthorizedMissing or invalid Authorization header / api_Token.
402Payment RequiredInsufficient token balance for this call. Top up
429Too Many RequestsRate limit exceeded for your tier (see /pricing for tier limits). Tier limits
500Server ErrorUnexpected server-side failure. Retry with backoff; report if persistent.

Code samples

curl -X POST "https://api.finradar.ai/api/v1/payments/create-intent" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"

Generate an API key in /account/credentials to run live queries (literal YOUR_API_KEY placeholder shown until then).