Proof of Financial Capacity
Submit Proof of Financial Capacity — required to raise a COMPANY subaccount's operating limits and to unlock USD (KYB USD).
One document, two unlocks. Proof of Financial Capacity (PoFC) is what evidences the volume a
COMPANYsubaccount asks for: it is required to raise the subaccount's operating limits and to enable USD operations. A single approved submission serves both. Its KYB must beAPPROVEDfirst.
Overview
Proof of Financial Capacity is a single compliance document that evidences the
company's ability to operate the declared volumes. You upload the document, submit
it, and poll the attempt until it is APPROVED.
For higher limits, PoFC is one of three documents — the other two are the company and UBO Proof of Address. For USD, PoFC alone is the gate. Either way the submission below is the same call.
What counts as Proof of Financial Capacity? A document that demonstrates the company's funds or income, such as a bank statement, an account balance statement, audited financial statements, or an accountant-issued income statement. Submit the statement that actually covers the volume you are asking for — a statement well below the declared volume is the most common rejection.
Format. A single-sided PDF, dated within the last 3 months. A document
collected during your own onboarding may be accepted up to 12 months old. The
optional inline document copy accepts up to ~10MB of binary.
POST /api/kyc/document— register the PDF (documentType: "PROOF-OF-FINANCIAL-CAPACITY") and get a one-time upload URL.POST /api/kyb/proof-of-financial-capacity— submit the uploaded document, get back anattemptId.GET /api/kyb/proof-of-financial-capacity/{attemptId}— poll the attempt (PENDING→APPROVEDorREJECTED).
Flow
- Confirm the
COMPANYsubaccount's KYB isAPPROVED. - Register a
PROOF-OF-FINANCIAL-CAPACITYdocument,PUTthe PDF to the returned URL, and pollGET /api/kyc/document/{documentId}untilready: true. POST /api/kyb/proof-of-financial-capacitywith thedocumentId— Hodle returns anattemptId.- Poll the
attemptIduntilstatus: APPROVED. USD operations unlock on approval; the higher operating limits also require both Proof of Address attempts to beAPPROVED.
Alternate rail
Accounts routed through the alternate limit-increase rail submit these same endpoints unchanged. The comprovante is routed to the configured limit-increase API — the rail is selected automatically from the platform account's configuration, and no request field toggles it. Other accounts use the default rail exactly as documented above.
On the alternate rail, POST /api/kyb/proof-of-financial-capacity accepts one extra
optional field, desiredMonthlyLimit — the requested ceiling in BRL cents. It is
ignored (accepted as a no-op) on the default rail, so a single client integration
works against both. When omitted, the configured rail uses a default ceiling of
R$ 500.000,00. If a Proof of Address request is already open for the same
company, this comprovante attaches to it instead of opening a second one — only one
open limit request per company is allowed at a time.
Uploading the document
Proof of Financial Capacity reuses the document upload
endpoints — only the documentType differs.
Register the document — POST /api/kyc/document
curl --request POST \
--url https://api.hodle.com.br/api/kyc/document \
--header "Authorization: Bearer $API_KEY" \
--header "Content-Type: application/json" \
--data '{ "subAccountId": "c852df87-...", "documentType": "PROOF-OF-FINANCIAL-CAPACITY" }'PUT the PDF to the returned uploadUrlFront, then poll readiness (see Uploading documents).
POST /api/kyb/proof-of-financial-capacity
Request
curl --request POST \
--url https://api.hodle.com.br/api/kyb/proof-of-financial-capacity \
--header "Authorization: Bearer $API_KEY" \
--header "Content-Type: application/json" \
--data '{
"subAccountId": "c852df87-ac61-4259-8242-6451658dfedb",
"uploadedPoFCId": "doc_pofc_9a1..."
}'const res = await fetch(
'https://api.hodle.com.br/api/kyb/proof-of-financial-capacity',
{
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.HODLE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ subAccountId, uploadedPoFCId }),
},
)
const data = await res.json()import os, requests
res = requests.post(
"https://api.hodle.com.br/api/kyb/proof-of-financial-capacity",
headers={
"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}",
"Content-Type": "application/json",
},
json={"subAccountId": sub_account_id, "uploadedPoFCId": uploaded_pofc_id},
)
data = res.json()Parameters
| Field | Type | Required | Description |
|---|---|---|---|
subAccountId | string | Yes | The COMPANY subaccount's id. |
uploadedPoFCId | string | Yes | documentId of the uploaded Proof of Financial Capacity PDF, with ready: true. |
sandboxReject | boolean | No | Sandbox only. true simulates a rejected attempt; ignored in production. |
desiredMonthlyLimit | integer | No | Alternate rail only. Requested monthly ceiling in BRL cents; defaults to 50000000 (R$ 500.000,00). No-op on the default rail. |
document | string | No | The PDF itself, base64-encoded, stored encrypted alongside the attempt. Optional — the document you uploaded with uploadedPoFCId is what gets reviewed. |
documentMimeType | string | No | MIME type of document, e.g. application/pdf. |
sandboxReject | boolean | No | Sandbox only. true makes the attempt resolve as REJECTED; ignored in production. |
Response
{
"success": true,
"data": {
"attemptId": "pofc_1e6b8f42-9c3d-4a71-b5e8-2f0d6c8a1b34",
"status": "PENDING",
"createdAt": "2026-07-22T22:00:00.000Z"
}
}The 202 means the document was accepted for review, not that it was approved.
Compliance reviews it and the outcome shows up on the poll below — see
Review timing.
Errors
{
"success": false,
"error": "Validation failed",
"details": [{ "field": "uploadedPoFCId", "message": "Required" }]
}{ "success": false, "error": "Subaccount not found for this platform" }A subaccount that belongs to another platform answers 404, not 403, so the status
code cannot be used to probe for ids you do not own. An attemptId you do not own
answers 404 { "error": "Attempt not found" }.
GET /api/kyb/proof-of-financial-capacity/{attemptId}
Poll the attempt until it resolves.
curl --request GET \
--url https://api.hodle.com.br/api/kyb/proof-of-financial-capacity/pofc_1e6b8f42-9c3d-4a71-b5e8-2f0d6c8a1b34 \
--header "Authorization: Bearer $API_KEY"const res = await fetch(
`https://api.hodle.com.br/api/kyb/proof-of-financial-capacity/${attemptId}`,
{ headers: { Authorization: `Bearer ${process.env.HODLE_API_KEY}` } },
)
const data = await res.json()import os, requests
res = requests.get(
f"https://api.hodle.com.br/api/kyb/proof-of-financial-capacity/{attempt_id}",
headers={"Authorization": f"Bearer {os.environ['HODLE_API_KEY']}"},
)
data = res.json()Response
{
"success": true,
"data": {
"attemptId": "pofc_1e6b8f42-9c3d-4a71-b5e8-2f0d6c8a1b34",
"status": "APPROVED",
"resultMessage": null,
"createdAt": "2026-07-22T22:00:00.000Z",
"updatedAt": "2026-07-22T22:01:43.000Z"
}
}{
"success": true,
"data": {
"attemptId": "pofc_1e6b8f42-9c3d-4a71-b5e8-2f0d6c8a1b34",
"status": "REJECTED",
"resultMessage": "Statement does not cover the declared capacity",
"createdAt": "2026-07-22T22:00:00.000Z",
"updatedAt": "2026-07-22T22:01:43.000Z"
}
}Status values
| Status | Meaning |
|---|---|
PENDING | Under review. Keep polling. |
APPROVED | Accepted. USD operations unlock; limits rise once the two Proof of Address attempts are APPROVED as well. |
REJECTED | Failed. resultMessage describes why; upload a fresh document and resubmit. |
resultMessage is only populated on REJECTED — it is null while pending and on
approval.
Review timing
Proof of Financial Capacity is reviewed by Hodle's compliance team before it reaches
the underlying provider, so PENDING is not a machine-speed state: an attempt can
sit there for hours. Poll on a slow interval (minutes, not seconds) and drive your
UI off the poll rather than off the POST response.
There is no webhook for this attempt. Unlike KYC and KYB, which
push kyc.completed, Proof of Financial Capacity is poll-only. Persist the
attemptId when you submit — it is the only handle you get on the review.
Because the review is human, a document submitted while the subaccount's KYB is
still not APPROVED is accepted with 202 and rejected later — you will not get a
synchronous error for it. Confirm KYB is APPROVED before submitting.
In the sandbox there is no human in the loop: the attempt resolves on submission,
so your first poll already returns APPROVED, or REJECTED with
resultMessage: "Sandbox: attempt rejected on request" when you pass
sandboxReject: true.