API & agent onboarding
Use these APIs when your integration creates a Flint account before the developer visits the Flint dashboard, or when your product embeds merchant onboarding. There are two tracks:
- Signup. Three calls take an email address to a sandbox-bound
flint_test_...key. They use an onboarding session that reaches only the merchant's sandboxes and expires after 24 hours. - Embedded onboarding. Your backend reads onboarding state, submits the merchant's onboarding intent, and hands verification to the account owner in an embedded browser component. Build and test it in a sandbox, then run the same loop against the live account with a live key.
Identity, business verification, documents, payout details, provider authentication, and provider agreement acceptance always stay in an embedded browser component owned by the account owner.
If you can use the dashboard, create a test API key there and start with Accept your first payment. This guide is for integrations that own merchant provisioning.
Local development with the CLI#
If you already have a Flint account, use browser login and verify your setup:
flint login
flint doctor
Confirm the terminal code in your browser, choose your merchant and sandbox, and approve the requested permissions. The CLI refreshes access automatically. Use flint login --no-open to open the link yourself, --profile NAME for another profile, and --live for explicit production access. flint logout --confirm revokes the OAuth session. See the CLI guide for session lifetimes and revocation.
Use flint signup to create an account from the terminal, flint auth import for an existing API key, or FLINT_API_KEY for CI.
Signup#
Signup is three calls:
POST /v1/onboarding/startemails a verification code.POST /v1/onboarding/verify-emailreturns an onboarding session token.POST /v1/onboarding/api-keyissues the first sandbox key.
flint signup runs these calls from a terminal.
Key issuance and provider verification are independent actions. The first key becomes available after email verification and sandbox provisioning. It does not bypass capability gates: payments and payouts remain unavailable until their requested capabilities are ready.
Before you start#
- Use
https://api.withflintpay.comfor both test and live requests. - Sandbox isolation comes from sandbox-bound
flint_test_...keys. - Keep the temporary
verification_tokenonly until email verification finishes. - Keep
onboarding_session_tokenon your backend and send it asAuthorization: Bearer. It is a sandbox-only setup credential, not a normal resource credential: it works on/v1/onboarding/...,/v1/merchant-account-sessions, the sandbox routes under/v1/developer/sandboxes, and partner app creation and install reads under/v1/developer/partner/apps, and does not authenticate requests like/v1/orders. It never reaches the live account.
An agent cannot finish the email step unless it can read the mailbox or hand the code to a human. The verification token expires after 15 minutes and allows five incorrect code attempts. Correct codes do not count against either limit, including retries with merchant_id after 409 MERCHANT_SELECTION_REQUIRED or after a server error. A wrong code on the fifth attempt returns INVALID_VERIFICATION; later attempts return VERIFICATION_ATTEMPTS_EXCEEDED, even with the correct code. Call POST /v1/onboarding/start with a new idempotency key to send a new code and get a new token. After 20 incorrect codes per email in one hour, both start and verify return 429 RATE_LIMIT_EXCEEDED; wait for the Retry-After delay before retrying. The start, verify, and key routes have tight per-IP and per-email throttles; see setup and onboarding throttles.
1. Start and verify email#
curl -X POST https://api.withflintpay.com/v1/onboarding/start \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onboarding-start-owner-example" \
-d '{
"email": "owner@example.com",
"first_name": "Jane",
"last_name": "Doe"
}'
Save data.verification_token, read the short-lived code from the email, then verify it:
curl -X POST https://api.withflintpay.com/v1/onboarding/verify-email \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onboarding-verify-owner-example" \
-d '{
"verification_token": "devver_v1.example",
"verification_code": "482193"
}'
Save data.onboarding_session_token, data.onboarding_session_expires_at, and data.default_sandbox_id. Flint provisions the user, merchant, direct owner membership, and default private sandbox if needed. merchant_created tells you whether this request created the merchant. The session lasts 24 hours, expires at onboarding_session_expires_at, and cannot be refreshed. Verify the email again to get a new session. The verification response's can_issue_api_key is accurate: it is true when the merchant has no external API key and a default sandbox exists. When it is true, issue the key next.
If verification returns 503 IDENTITY_UNAVAILABLE, retry after a short delay. For 500 IDENTITY_RESOLUTION_FAILED, contact Flint support. If the email already has a Flint account, signup returns 409 EMAIL_ALREADY_LINKED: sign in to that account with flint login or the dashboard instead.
2. Issue the first sandbox key#
curl -X POST https://api.withflintpay.com/v1/onboarding/api-key \
-H "Authorization: Bearer $ONBOARDING_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onboarding-first-key-owner-example" \
-d '{"name":"First backend integration"}'
POST /v1/onboarding/api-key accepts sandbox_id in the JSON body or query string. A non-empty body value takes precedence. If you omit sandbox_id, Flint binds the key to the default sandbox. The starter scopes include onboarding read, onboarding write, and merchant-account-session write authority so the key can drive embedded onboarding in its sandbox. Store secret_key immediately because it is shown once. The operation issues one key per account: once it has, it returns 409 INITIAL_API_KEY_ALREADY_CREATED. Run flint login, or create another key on the dashboard's API keys page.
In onboarding state, the same operation appears as the create_api_key action while can_issue_api_key is true. It lists name in required_fields, returns submit_method: "POST", and its submit_endpoint includes the sandbox_id query parameter when a sandbox is selected.
Use this key for ordinary sandbox API development, but expect capability-dependent calls to fail until onboarding makes the corresponding capability ready. For example, a merchant that did not request card acceptance receives CAPABILITY_NOT_REQUESTED from card-payment operations.
Signup ends here. To take real payments, verify the business and create a live key in the dashboard; see Going live.
Embedded onboarding#
The onboarding state machine takes a merchant from account intent through embedded verification. Run it in a sandbox to build and test your onboarding UI with sandbox verification values. Once you have a live key, run the same loop against the live account. You can create a live key in the dashboard before the business is verified. It can take live card payments as soon as Stripe turns on card payments for the account, when the accept_card_payments capability is ready. Payouts can turn on later, once Stripe confirms the bank account.
The credential you send decides which account the loop reaches:
| Credential | Reaches |
|---|---|
| Onboarding session token | The merchant's default sandbox, or another sandbox of the same merchant named by sandbox_id. Never the live account. |
Sandbox key (flint_test_...) | Its own sandbox. Never the live account. |
Live key (flint_live_...) | The live account. Omit sandbox_id. |
A session request without sandbox_id targets the default sandbox. If the merchant has no default sandbox, it returns SANDBOX_SELECTION_REQUIRED; pass sandbox_id or create a sandbox with POST /v1/developer/sandboxes. API keys need merchants.onboarding.read to read state, merchants.onboarding.write to advance, and merchants.account_sessions.write to create and refresh account sessions.
1. Read the state machine#
curl https://api.withflintpay.com/v1/onboarding/state \
-H "Authorization: Bearer $ONBOARDING_SESSION_TOKEN"
Drive the integration from these fields:
| Field | Meaning |
|---|---|
status | Overall workflow class: needs_input, needs_external_action, waiting_for_review, ready_for_api_key, or complete. |
next_step | The single primary onboarding action. |
pending_steps | Ordered onboarding work after the primary action. |
available_actions | Safe actions that can run concurrently with next_step, such as initial key issuance. |
can_issue_api_key | Whether the one-time onboarding key operation is currently allowed. |
requirements | Normalized Flint requirement IDs. |
A state can be needs_external_action and also have can_issue_api_key: true. Do not wait for provider verification to issue the initial key.
next_step is the first onboarding action. When no onboarding actions remain and the first key can be issued, status is ready_for_api_key with create_api_key as next_step, even if review is pending or onboarding is complete. After the key exists, status reflects review or completion.
2. Submit account intent#
When next_step.machine_completable is true, call next_step.submit_method at next_step.submit_endpoint with the fields in required_fields. Steps with a submit endpoint include its method; browser and review steps omit both. Initial account provisioning uses POST /v1/onboarding/advance:
curl -X POST https://api.withflintpay.com/v1/onboarding/advance \
-H "Authorization: Bearer $ONBOARDING_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onboarding-advance-owner-example" \
-d '{
"profile": {
"support_email": "help@your-business.example",
"support_phone": "+12125550123",
"website_url": "https://your-business.example"
},
"country": "US",
"requested_capabilities": [
"accept_card_payments",
"receive_payouts"
],
"sandbox_id": "test_01JQEXAMPLEDEFAULT12345678"
}'
Use a real, publicly reachable business website. Reserved example domains above illustrate the JSON shape but do not pass provider website review.
requested_capabilities is an exact Flint capability set:
- Omit it for the baseline
accept_card_paymentsplusreceive_payoutsset. - The baseline pair is the only supported initial set. Flint returns
CAPABILITY_SET_UNSUPPORTEDforreceive_payoutsalone before creating an account. accept_card_paymentsrequiresreceive_payouts; Flint returnsCAPABILITY_DEPENDENCY_REQUIREDinstead of silently adding it.- Unknown and provider-shaped values are rejected.
Country and capability intent become immutable when provisioning starts. Retrying the same request is idempotent. A different country or capability set returns an explicit immutable-intent error and never creates a replacement account.
If advance fails with a network error, a timeout, or a 5xx response, send the same request again with the same Idempotency-Key. Flint resumes the account setup that request started: the retry finishes it or creates the account once, and never creates a second account. Keep the same merchant and onboarding session; there is nothing to start over.
If Flint returns ACCOUNT_SETUP_CONFIGURATION_CONFLICT or ACCOUNT_SETUP_REPAIR_REQUIRED, contact Flint support before retrying or continuing onboarding. Retrying advance does not clear either error.
Do not send legal-entity artifacts, account tokens, identity fields, documents, bank details, or agreement attestations to advance. Those fields belong in embedded Account Onboarding and unknown request fields are rejected.
3. Hand the browser step to the owner#
When state returns next_step.launch, copy that launch exactly. It names the component, recommended policy, endpoint, and sandbox. launch.endpoint uses the same public API base as submit_endpoint: absolute when configured, relative otherwise. Create the browser credentials only from your backend:
curl -X POST https://api.withflintpay.com/v1/merchant-account-sessions \
-H "Authorization: Bearer $FLINT_TEST_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: account-session-owner-example" \
-d '{
"components": ["account_onboarding"],
"collection_strategy": "incremental",
"future_requirements": "omit",
"sandbox_id": "test_01JQEXAMPLEDEFAULT12345678"
}'
Authenticate the human in your product and verify that they are allowed to manage this merchant before returning the session response to their browser. A secret Flint key is machine authority to mint sensitive browser access. Do not expose the key itself, accept a merchant ID as authorization, or put the client secret or launch token in a link.
Initialize Connect.js with loadConnectAndInitialize({ publishableKey, fetchClientSecret }), using data.client_session.stripe.publishable_key and data.client_session.stripe.account_session.client_secret. The account session names this operation as stripe_js_call: "load_connect_and_initialize". For each policy-aware component, map collection_options to setCollectionOptions({ fields, futureRequirements, requirements }), taking futureRequirements from collection_options.future_requirements.
Connect.js can request a new client secret while the component remains mounted. Your backend sends the latest data.launch_token to the dedicated endpoint:
curl -X POST https://api.withflintpay.com/v1/merchant-account-sessions/refresh \
-H "Authorization: Bearer $FLINT_TEST_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: account-session-refresh-owner-example" \
-d '{"launch_token":"aslaunch_v1.example"}'
Refresh with the API key that created the launch token. A launch token created with an onboarding session refreshes with an onboarding session for the same user and stays in the sandbox it was created for.
See Merchant Account Sessions for executable vanilla and React recipes, token rotation, multi-component sessions, CSP, authentication popups, headless secure-link design, and sandbox verification values.
Partner-install tokens cannot create or refresh account sessions. The partner OAuth token represents an installation, not an authenticated human with authority to enter the merchant's provider account.
4. Reconcile after browser exit#
onExit means only that the browser left Account Onboarding. It does not prove submission, provider authentication, or readiness. Read state immediately, then poll at 2, 4, 8, and 15 seconds, followed by every 30 seconds for up to 15 minutes.
Stop polling when Flint returns a machine-completable step, another browser launch, complete, or a terminal error. If only pending_verification remains, wait. Do not mint another session just because review is asynchronous. merchant.readiness.updated is an at-least-once hint to fetch current state sooner, not a complete readiness payload.
Run the loop for the live account#
Create a live key in the dashboard with the onboarding and account-session scopes listed above; see Going live. The business does not need to be verified first: this loop is how the live key drives verification, and the key can take live card payments once accept_card_payments is ready. Send the same state, advance, and account-session requests with the live key and omit sandbox_id. The onboarding session token and sandbox keys cannot reach live onboarding.
Targeted remediation#
The same loop handles later requirements. The merchant's onboarding_status stays completed once reached, even when Stripe later asks for more details, so read requirements and next_step rather than the status. Read onboarding state after merchant.readiness.updated and follow next_step.launch. Use a live key for live remediation; a sandbox key rehearses the same flow in its sandbox. When a next action names selected requirements, send the Flint IDs exactly:
curl -X POST https://api.withflintpay.com/v1/merchant-account-sessions \
-H "Authorization: Bearer $FLINT_LIVE_SECRET_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: remediation-business-website-example" \
-d '{
"components": ["account_onboarding"],
"collection_strategy": "incremental",
"future_requirements": "omit",
"targeted_requirement_ids": ["business_website"]
}'
Flint maps public requirement IDs to provider collection options. If an ID is unknown or cannot be mapped, Flint fails closed before returning a client secret. Never catch that error and broaden the session to collect everything. Fetch new state and show the corrected action.
Account Management is the general business and compliance surface. Notification Banner exposes ongoing risk and compliance actions. Account Onboarding is the focused collection surface. Payout destination changes after initial onboarding use the Payouts component.
Provider-originated email actions#
Ordinary headless onboarding can stay in your authenticated UI. Provider-originated compliance, risk, payout, payment, account, and document emails always route through Flint's authenticated action gateway because those links are configured at the platform level. The recipient signs in to Flint, the provider reference is resolved server-side and removed from the browser URL, and Flint mints the authorized embedded component.
The primary owner must retain a Flint login even when the rest of the integration is headless. Do not attempt to intercept or rewrite provider email links into your own session flow.
Agent loop#
- Start signup and wait for the email code.
- Verify email and store the onboarding session token on your backend.
- If
can_issue_api_keyis true, create and store the sandbox key without waiting for verification. - Read onboarding state in the sandbox.
- If
next_step.machine_completableis true, submit it and drive from the returned state. - If
next_step.launchexists, require the account owner, create the embedded session, and wait for browser exit. - Poll state. Wait when only review is pending.
- Use the sandbox key for normal
/v1/...work in its sandbox. Live onboarding and remediation use a live key created in the dashboard, which you can create before the business is verified.
Next steps#
- CLI:
flint signupruns the signup calls, if a terminal is an acceptable place to complete them - Onboarding API Reference
- Merchant Account Sessions
- Sandboxes API Reference
- Authentication
- Testing
- Going live
