Run onboarding for an account
This guide walks through onboarding an Account from scratch: create the account with the capabilities you need, hand off to a Frame-hosted onboarding session to collect the rest of the information, and verify on the way back.
The shape of the flow is the same whether you're onboarding a merchant onto your platform, a seller on a marketplace, or a creator opting into payouts. The difference is which capabilities you request — Frame scopes the hosted UI to exactly what those capabilities need.
Prerequisites
| Requirement | Details |
|---|---|
| Frame API key | Use a secret key for server-to-server calls. Test mode keys hit Frame's sandbox; production keys hit live. |
| Capabilities decided | Know which capabilities the account needs before you create it (e.g. card_send for buyers, card_receive + kyc for payout recipients). See capabilities. |
| Return URL | A URL on your application where Frame should redirect the account holder after they complete or exit the session. |
1. Create the account
Create an account with the type, any capabilities you want to request, and whatever profile information you already have. Prefill aggressively — every field you supply is one less the account holder has to fill in.
Pass type, capabilities, and a profile sub-object that matches the type (individual or business). Set external_id if you want to pin this Frame account to a record in your own system.
If you already know the account holder has accepted your terms of service (for example, they ticked a box during signup), include the terms_of_service object so the universal ToS requirement is satisfied from the start.
curl --request POST \
--url https://api.framepayments.com/v1/accounts \
--header 'Authorization: Bearer SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"type": "individual",
"capabilities": ["kyc", "card_receive"],
"external_id": "usr_8675309",
"profile": {
"individual": {
"email": "marcia@example.com",
"name": {
"first_name": "Marcia",
"last_name": "Longo"
},
"phone": {
"number": "3107484186",
"country_code": "1"
}
}
},
"terms_of_service": {
"accepted_at": "2026-06-03T14:15:22Z",
"ip_address": "192.168.1.1",
"user_agent": "Mozilla/5.0"
}
}'
The response is the freshly-created Account. Save its id — you'll use it to create the onboarding session.
2. Inspect what's still outstanding
Each capability on the new account has a currently_due array listing the fields the account holder still needs to supply. If it's empty across every capability, you're done — no session needed. If not, those are the inputs the onboarding session will collect.
You don't have to query this explicitly before creating the session — Frame's hosted flow scopes itself based on the same data. But inspecting it is useful when you want to surface progress or skip the redirect if there's nothing left to collect.
{
"name": "kyc",
"status": "pending",
"currently_due": [
"profile.individual.address",
"profile.individual.birthdate",
"profile.individual.ssn_last_four"
]
}
3. Create an onboarding session
Create a session for the account and supply a return_url for Frame to redirect back to.
The session is valid for 30 minutes after creation. Within that window the URL can be reopened if the account holder bounces off mid-flow; once it expires, the URL stops working and you'll need to create a new session. Generate the session immediately before redirecting — not at account creation time — so the clock doesn't burn down before the account holder reaches it.
The response includes a url field. That's where you send the account holder.
curl --request POST \
--url https://api.framepayments.com/v1/onboarding_sessions \
--header 'Authorization: Bearer SECRET_KEY' \
--header 'Content-Type: application/json' \
--data '{
"account_id": "99c6b0da-2570-42a7-838a-5eaa318b07df",
"return_url": "https://yourapp.com/onboarding/complete"
}'
{
"id": "os_7f3a1b2c-4d5e-6f7a-8b9c-0d1e2f3a4b5c",
"object": "onboarding_session",
"account_id": "99c6b0da-2570-42a7-838a-5eaa318b07df",
"url": "https://os.framepayments.com/onboarding?client_secret=onb_sess_EXAMPLE_CLIENT_SECRET",
"steps": ["kyc"],
"expires_at": 1721012405,
"livemode": false
}
4. Redirect the account holder
Redirect from your application to the session URL. Don't email or SMS the URL — it grants access to the account holder's verification flow and should only ever leave your authenticated app via a direct redirect.
// Server returns the session URL after creation
window.location.href = session.url;
Frame collects whatever the requested capabilities need — name, address, DOB, last 4 of SSN for KYC; bank account number and routing for bank_account_receive; document upload for higher-risk verifications; ToS acceptance if applicable. The account holder works through the steps, then Frame redirects them back to your return_url.
5. Verify on the return
The redirect fires when the account holder clicks through, not when every check has resolved. KYC and bank verification can take seconds to minutes to settle. Don't treat the redirect as a success signal — retrieve the account and inspect capability statuses, or listen for webhooks.
Two ways to know when the account is fully active:
- Poll: retrieve the account after the redirect and check
statusplus each capability'sstatusandcurrently_due. Repeat with backoff if anything is stillpending. - Webhooks: subscribe to
account.activated,capability.activated, andcapability.disabled. Frame fires them as checks resolve (account.activatedrolls up when all capabilities are active; the capability-level events fire per transition). This is the preferred pattern for production — no polling, push-based.
curl --request GET \
--url https://api.framepayments.com/v1/accounts/99c6b0da-2570-42a7-838a-5eaa318b07df \
--header 'Authorization: Bearer SECRET_KEY'
If the account status is still pending and capabilities still have currently_due entries, the account holder dropped out partway. Create a fresh session and redirect them again — Frame scopes the new session to whatever remains outstanding, so they won't re-enter data already on file.
Common variations
Adding capabilities later. When an account graduates to a new flow (a buyer opting into payouts, for example), request the additional capability with a PATCH to the account, then create a new onboarding session for the deltas.
Skipping the hosted flow. If you want to collect everything via API yourself, update the account with the profile fields rather than redirecting. You'll need to handle document upload UX and any front-end verification SDKs on your own — Frame doesn't validate documents passed via raw API.
Business onboarding. For type: business, Frame runs KYB on the entity and onboards each owner or controller as a separate individual account in parallel. The session URL handles both flows; structurally the same call.
Next steps
- Once
kycis active, see Run KYC for re-running KYC against an already-onboarded account (re-verification, periodic refresh). - For payout-recipient onboarding specifically, see Pay out to an account.