Nominal accounts

Open a real bank account in a subaccount's own name, and follow its approval and KYB.

Production only. A nominal account is a real account opened at a regulated institution under a real document. There is no sandbox equivalent — sandbox-api.hodle.com.br answers 422 SANDBOX_UNAVAILABLE before anything is written. See Sandbox.

Overview

A subaccount identifies one of your end-users. A nominal account gives that end-user a bank account in their own name: their CNPJ on the account, their own Pix keys, their own balance — not a ledger position inside yours.

One subaccount holds at most one nominal account, and a subaccount is addressed the same way everywhere else in the API, so the subAccountId you already hold is the only identifier you need. Every operation that names it is routed to that account's own credentials, never to your main account's.

  • POST /api/subaccount/{subAccountId}/account — open the account. Returns an onboardingUrl for the end-user to complete.
  • GET /api/subaccount/{subAccountId}/account — read the account and its status. There is no outbound event for this lifecycle yet, so poll it.

Lifecycle

POST /api/subaccount/{id}/account


   PENDING ──── end-user completes onboardingUrl ────┐

                                          institution approves

                             Hodle provisions this account's own
                             credentials + webhooks, opens its
                             stablecoin subaccount, starts the KYB


                                          PENDING ──► APPROVED

status stays PENDING from creation until the KYB completes. APPROVED is the only state in which the account can transact; REJECTED carries a rejectionReason.

The KYB is not something you start. Approval of the account register is what triggers it, and the hosted forms it produces are reported on the account.

POST /api/subaccount/{subAccountId}/account

curl --request POST \
  --url https://api.hodle.com.br/api/subaccount/c852df87-ac61-4259-8242-6451658dfedb/account \
  --header "Authorization: Bearer $API_KEY" \
  --header "Content-Type: application/json" \
  --data '{
    "companyName": "Acme Ltda.",
    "taxId": "12345678000199",
    "representativeTaxId": "12345678901"
  }'
FieldTypeRequiredDescription
companyNamestringYesLegal name of the company the account is opened for. Up to 120 chars.
taxIdstringYesThe company's CNPJ, digits only.
representativeTaxIdstringNoCPF of the legal representative who will complete the onboarding.
201 Created
{
  "success": true,
  "data": {
    "accountRef": "68c0f1a2b3c4d5e6f7a8b9c0",
    "subAccountId": "c852df87-ac61-4259-8242-6451658dfedb",
    "companyName": "Acme Ltda.",
    "taxId": "12345678000199",
    "status": "PENDING",
    "stableSubAccountStatus": null,
    "accountId": null,
    "onboardingUrl": "https://onboarding.woovi.com/...?embed=true",
    "rejectionReason": null,
    "createdAt": "2026-09-21T14:03:11.000Z"
  }
}

Send the end-user to onboardingUrl. Nothing else happens until they finish it.

StatusBodyMeaning
404Subaccount not found for this platformThe subaccount is not yours. Not a hint that it exists.
409errorCode: ACCOUNT_ALREADY_EXISTSThis subaccount already has a nominal account.
422errorCode: SANDBOX_UNAVAILABLECalled against the sandbox. Nothing was written.

GET /api/subaccount/{subAccountId}/account

curl --url "https://api.hodle.com.br/api/subaccount/c852df87-ac61-4259-8242-6451658dfedb/account" \
  --header "Authorization: Bearer $API_KEY"
200 OK
{
  "success": true,
  "data": {
    "accounts": [
      {
        "accountRef": "68c0f1a2b3c4d5e6f7a8b9c0",
        "subAccountId": "c852df87-ac61-4259-8242-6451658dfedb",
        "companyName": "Acme Ltda.",
        "taxId": "12345678000199",
        "status": "APPROVED",
        "stableSubAccountStatus": "CONFIRMED",
        "accountId": "acc_9f2b...",
        "onboardingUrl": "https://onboarding.woovi.com/...?embed=true",
        "rejectionReason": null,
        "createdAt": "2026-09-21T14:03:11.000Z"
      }
    ]
  }
}

stableSubAccountStatus tracks the stablecoin leg separately from the account itself: IN_REVIEW and AWAITING_DOCUMENTS mean the institution is still reviewing it, and only CONFIRMED together with status: APPROVED means the account can move money.

Why the account is yours to open but not yours to spend

The credentials minted for a nominal account belong to that account. They are never returned by this API and never shared with your main account: a request that names the subaccount is signed with that account's own key, and its Pix and stablecoin webhooks are registered against it. That is what keeps one end-user's balance out of another's, and out of yours.