Account Opening — Individuals (PF)
This guide walks through opening an individual account using the default CorpX biometrics flow: we generate a facial capture link, you deliver it to the end user, and once the facial check is approved the account is opened automatically — no manual review.
Flow
Step 1: Create the accreditation
Request
curl -X POST "https://tenant.api.corpx.com/v1/accreditations/pf" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: onboard-customer-12345" \
-d '{
"person": {
"name": "Maria da Silva",
"cpf": "12345678901",
"birthDate": "1990-05-20",
"email": "maria@email.com",
"phone": "+5511999998888",
"monthlyIncome": 5000.00
},
"address": {
"zipCode": "01310100",
"street": "Avenida Paulista",
"number": "1000",
"complement": "Apto 42",
"neighborhood": "Bela Vista",
"city": "São Paulo",
"state": "SP",
"cityIbgeCode": "3550308"
}
}'
Parameters
| Field | Type | Required | Description |
|---|---|---|---|
person.name | string | Yes | Holder's full name. |
person.cpf | string | Yes | CPF (11 digits, no punctuation). |
person.birthDate | string | Yes | Birth date (YYYY-MM-DD). |
person.email | string | Yes | Holder's email. |
person.phone | string | Yes | Phone in DDI + area code + number format (e.g. +5511999998888). |
person.monthlyIncome | number | No | Declared monthly income (BRL). |
address.* | object | Yes | Full address. neighborhood is required (or legacy district). cityIbgeCode is the 7-digit IBGE city code. |
biometry | object | No | Only for the Bring Your Own biometrics flow. |
callbackUri | string | No | Where to take the holder back to at the end of the journey. Requires prior registration with support — see Returning to your app. |
Initial PIX limits are applied automatically with conservative defaults. Limit changes (increase or decrease) require a manual request to support.
Response (201)
{
"accreditationId": "acr_9f8e7d6c5b4a",
"type": "pf",
"status": "PENDING_BIOMETRY",
"persons": [
{
"personId": "prs_3b1c9a7f2e4d",
"cpf": "12345678901",
"name": "Maria da Silva",
"biometryStatus": "PENDING",
"biometryLink": "https://cadastro.unico.app/process/abc123...",
"linkExpiresAt": "2026-07-14T18:00:00Z"
}
],
"createdAt": "2026-07-07T18:00:00Z"
}
The personId is the person's stable identifier within the accreditation. Use it to correlate the return to your app (see Returning to your app) — it is the personId, not the CPF, that appears in the return URI.
While status is PENDING_BIOMETRY, the CPF is not bound to the tenant and accountId does not exist yet. POST does not deduplicate — repeating with the same CPF creates another accreditation. Store the accreditationId and cancel abandoned pendings with POST /v1/accreditations/{id}/cancel. The accountId appears only after biometry is confirmed (accreditation.updated / accreditation.active webhooks).
The biometryLink is issued within seconds, but asynchronously — in rare cases it may be absent from the POST response. When that happens, receive it via the accreditation.biometry.link.created webhook or call the GET right after.
If the CPF is already an account holder and your tenant has automatic portability enabled, the response comes back with status: "PENDING_CONSENT" and a consentLink instead of the biometryLink — there is no new account to open, but rather an authorization from the holder for you to operate the account they already have. Without that enablement (the default), the accreditation fails with existing_partner_account and portability goes through support. See Holder who already has an account below.
Step 2: Deliver the facial link
Deliver the biometryLink to the end user through your preferred channel (inside your app, WhatsApp, SMS, email). The capture journey runs in the phone's browser, no app installation required.
- The link expires in 7 days (
linkExpiresAt). If it expires, use the retry endpoint (Step 4). - The same link is also delivered in the
accreditation.biometry.link.createdwebhook.
Step 3: Wait for completion
Once the user completes the capture and the biometrics are approved, approval is automatic — there is no manual review for PF in the default flow. Track it through webhooks:
accreditation.updated—PENDING_BIOMETRY→INTEGRATING;accreditation.active— account opened; theaccountIdis ready to operate.
If the biometrics are rejected or expire, you receive accreditation.updated with the person's details and can retry.
You can also poll:
curl -X GET "https://tenant.api.corpx.com/v1/accreditations/pf?document=12345678901" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany"
{
"items": [
{
"accreditationId": "acr_9f8e7d6c5b4a",
"accountId": "c283eb5a-58da-47ef-844a-f36e51f078d8",
"type": "pf",
"status": "ACTIVE",
"persons": [
{
"cpf": "12345678901",
"name": "Maria da Silva",
"biometryStatus": "APPROVED"
}
],
"createdAt": "2026-07-07T18:00:00Z",
"updatedAt": "2026-07-07T18:25:41Z"
}
]
}
Step 4: Retry (expired link or rejected biometrics)
If the link expired or the capture failed, generate a new link for the person:
curl -X POST "https://tenant.api.corpx.com/v1/accreditations/acr_9f8e7d6c5b4a/persons/12345678901/retry" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany"
The response carries the new biometryLink and the new linkExpiresAt. The accreditation.biometry.link.created event is also re-emitted.
Step 5: Cancel (abandoned pending)
If the user abandoned before biometry, cancel the accreditation while status is PENDING_BIOMETRY (or PENDING_CONSENT):
curl -X POST "https://tenant.api.corpx.com/v1/accreditations/acr_9f8e7d6c5b4a/cancel" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-yourcompany" \
-H "Content-Type: application/json" \
-d '{"reason":"customer abandoned onboarding"}'
It ends as FAILED with errorReason=cancelled (accreditation.updated / accreditation.failed webhooks). After biometry is confirmed, cancel is no longer allowed (409 invalid_state).
Holder who already has an account (consent)
When the CPF is already an account holder, there is no new account to open. The path becomes the holder authorizing you to operate the account they already have — and that authorization is given by them, on a page hosted by us, with identity verification.
The POST /v1/accreditations/pf call is the same: you do not need to detect anything beforehand. What changes is the response.
Automatic portability is not enabled by default. It transfers control over the money in an account that already exists, so it depends on a specific contract and is enabled per tenant by our team.
Without it, a CPF that already has an account still ends in FAILED with errorReason=existing_partner_account and the message instructing the holder to request portability from support — in that case we run the process manually and no consentLink is issued.
To enable it, talk to your CorpX account contact.
Response (201) in consent mode
{
"accreditationId": "acr_5c4b3a2f1e0d",
"type": "pf",
"status": "PENDING_CONSENT",
"persons": [
{
"personId": "prs_3b1c9a7f2e4d",
"cpf": "12345678901",
"name": "Maria da Silva",
"biometryStatus": "PENDING",
"consentStatus": "PENDING",
"consentLink": "https://tenant.api.corpx.com/v1/accreditations/consent/cst_...",
"consentExpiresAt": "2026-07-30T18:00:00Z"
}
],
"createdAt": "2026-07-23T18:00:00Z"
}
| Field | Description |
|---|---|
status | PENDING_CONSENT — waiting for the holder's authorization. |
persons[].consentLink | Authorization page. Deliver it to the holder just like you already deliver the facial link. |
persons[].consentStatus | PENDING, ACCEPTED, DECLINED, DENIED or EXPIRED. |
persons[].consentExpiresAt | Link expiry (7 days). The journey as a whole expires in 30 days. |
The same link arrives in the accreditation.consent.link.created webhook — subscribe to that event if your integration picks links up from webhooks, because accreditation.updated carries no link at all.
While the journey is in PENDING_CONSENT, repeating POST /v1/accreditations/pf for the same CPF answers 409 already_accredited — with accountId: null, status: "PENDING_CONSENT" and the accreditationId of the journey already in progress. It is not an existing account in your tenant: it is the authorization still pending with the holder.
To recover the link instead of re-posting:
GET /v1/accreditations/{accreditationId}returns the current link inpersons[].consentLink, along withconsentExpiresAt.POST /v1/accreditations/{accreditationId}/persons/{cpf}/retryreissues the link with a fresh 7-day validity (the same endpoint as Step 4), as long as the 30-day journey has not expired.
If you really mean to abandon the journey, cancel it with POST /v1/accreditations/{accreditationId}/cancel (Step 5) before creating another one.
Flow
What the holder sees
- Before any capture, the page states who is requesting access and what the authorization allows — including moving and withdrawing the money in the account.
- Identity verification: a live selfie checked against official records by CPF. The page does not move forward without it.
- Only after the identity is confirmed do the account number and current balance appear — so the holder authorizes knowing exactly which money you will be operating.
- Explicit authorization, recorded with date, time, device and the balance that was disclosed.
The authorization is only valid in the same session and on the same device that went through the identity verification. Forwarding the link to someone else, replaying the page's POST or reusing an older verification authorizes nothing.
After the authorization
You receive accreditation.active with sharedAccount: true and an accountId to operate normally. Two important points:
- It is the same account, not a copy: balance, statement and Pix keys are the ones that already existed. The
accountIdis that account's identifier within your tenant. - Whoever already operated the account keeps operating it. Every tenant authorized by the holder receives the webhooks for each movement — including those for operations that did not originate from your API (which arrive with
external: true).
If the holder does not authorize, the accreditation ends as FAILED with one of these errorReason values: consent_declined, consent_expired, consent_facial_failed, castle_deny or consent_link_failed (details in Onboarding Webhooks).
The holder can revoke a granted access at any time through support. From then on, the revoked tenant's calls for that account return 403 — the account stays active for the holder and for the other authorized tenants.
Account ready
With status ACTIVE, the account is bound to your tenant and ready to operate. Use the accountId on all other API endpoints — PIX, statement, boletos, QR Codes, etc.
Store the accreditationId + accountId pair in your system. The accountId is the identifier you will use for every banking operation from then on.