/api/v1/payments/create-intentCreate 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
Common use case
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
| Name | In | Required | Default | Allowed | Description | Example |
|---|---|---|---|---|---|---|
| amount | body | required | — | — | Top-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
| Field | Type | Nullable | Description |
|---|---|---|---|
| status | string | no | ApiResponse envelope status — `success` on 200, `error` on 4xx/5xx. Always present in the standardised `ApiResponse` wrapper used across `/api/v1/*`. |
| request_id | string | yes | Per-request correlation ID (set by Flask `g.request_id`). Pass in support tickets to look up the call in server logs. |
| timestamp | string | no | ISO-8601 UTC timestamp the response was generated. |
| data | object | no | Payload object containing the Stripe client secret. |
| data.clientSecret | string | no | Stripe 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
| Status | Label | Description |
|---|---|---|
| 200 | OK | Request succeeded. |
| 400 | Bad Request | Invalid query, body, or path parameter. |
| 401 | Unauthorized | Missing or invalid Authorization header / api_Token. |
| 402 | Payment Required | Insufficient token balance for this call. Top up |
| 429 | Too Many Requests | Rate limit exceeded for your tier (see /pricing for tier limits). Tier limits |
| 500 | Server Error | Unexpected 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).