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 COMPANY subaccount 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 be APPROVED first.

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 an attemptId.
  • GET /api/kyb/proof-of-financial-capacity/{attemptId} — poll the attempt (PENDINGAPPROVED or REJECTED).

Flow

  1. Confirm the COMPANY subaccount's KYB is APPROVED.
  2. Register a PROOF-OF-FINANCIAL-CAPACITY document, PUT the PDF to the returned URL, and poll GET /api/kyc/document/{documentId} until ready: true.
  3. POST /api/kyb/proof-of-financial-capacity with the documentId — Hodle returns an attemptId.
  4. Poll the attemptId until status: APPROVED. USD operations unlock on approval; the higher operating limits also require both Proof of Address attempts to be APPROVED.

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

FieldTypeRequiredDescription
subAccountIdstringYesThe COMPANY subaccount's id.
uploadedPoFCIdstringYesdocumentId of the uploaded Proof of Financial Capacity PDF, with ready: true.
sandboxRejectbooleanNoSandbox only. true simulates a rejected attempt; ignored in production.
desiredMonthlyLimitintegerNoAlternate rail only. Requested monthly ceiling in BRL cents; defaults to 50000000 (R$ 500.000,00). No-op on the default rail.
documentstringNoThe PDF itself, base64-encoded, stored encrypted alongside the attempt. Optional — the document you uploaded with uploadedPoFCId is what gets reviewed.
documentMimeTypestringNoMIME type of document, e.g. application/pdf.
sandboxRejectbooleanNoSandbox only. true makes the attempt resolve as REJECTED; ignored in production.

Response

202 Accepted
{
  "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

400 — validation failed
{
  "success": false,
  "error": "Validation failed",
  "details": [{ "field": "uploadedPoFCId", "message": "Required" }]
}
404 — subaccount not found
{ "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

status: APPROVED
{
  "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"
  }
}
status: REJECTED
{
  "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

StatusMeaning
PENDINGUnder review. Keep polling.
APPROVEDAccepted. USD operations unlock; limits rise once the two Proof of Address attempts are APPROVED as well.
REJECTEDFailed. 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.