For agents (internet-banking account API)

Read this page before generating a client. The human guide starts at overview. Contract: /openapi/ib.yaml (OpenAPI 3.1, only the operations this credential can call; every operation also carries x-audience). Machine indexes: /llms.txt and /.well-known/api-catalog. Do not invent paths.

Facts

ItemValue
Audienceib (credential issued in the bank’s internet banking)
API hosthttps://client.api.corpx.com for every /v1/** call
TokenPOST https://auth.api.corpx.com/oauth2/token (client_credentials). Reuse until expires_in (300s)
Required headersAuthorization, X-Tenant-Id, X-Request-Timestamp, X-Content-SHA256, X-Request-Signature
Writesalso Idempotency-Key (part of the canonical string)
Accountsexactly one accountId
Webhookscreate requires accountId (422 account_id_required if omitted)
PIN headersdo not send X-Transaction-Pin or X-Acting-Document
Legacy PIXdo not call POST /v1/pix-out (accountId in the body). Use POST /v1/accounts/{accountId}/pix/out
Wrong hosthttps://tenant.api.corpx.com403 signed_host_required
Grace18h after issuance → 403 credential_not_yet_active is expected
CurrencyBRL, at most 2 decimal places

Signing algorithm, canonical string, Node sign(), and an offline test vector: request-signing.

End-to-end flow

  1. Token
POST https://auth.api.corpx.com/oauth2/token
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&client_id={clientId}&client_secret={clientSecret}
{ "access_token": "eyJ…", "expires_in": 300, "token_type": "Bearer" }
  1. Build the canonical string (LF, not CRLF):
METHOD\nPATH?QUERY\nTIMESTAMP\nIDEMPOTENCY_OR_EMPTY\nSHA256_HEX

Empty body SHA-256 is always e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855. Sign as detached JWS (<protected>..<signature>), ES256 = 64-byte R||S (not DER). Put kid in the protected header.

  1. Self-test (creates nothing; valid: false is still HTTP 200):
POST https://client.api.corpx.com/v1/security/signature/verify
Authorization: Bearer {access_token}
X-Tenant-Id: {tenantId}
X-Request-Timestamp: {unixSeconds}
X-Content-SHA256: {sha256(jsonBody)}
X-Request-Signature: {jws}
Content-Type: application/json
{
"method": "POST",
"path": "/v1/accounts/acc-123/pix/payments",
"timestamp": "1789412400",
"idempotencyKey": "4f1e3b7a-9d2c-4a11-8f55-2b0c6a7d1e90",
"body": "{\"amount\":1000}",
"signature": "{jws}"
}

Compare canonicalString character-by-character when valid is false.

  1. Balance
GET https://client.api.corpx.com/v1/accounts/{accountId}/balance
Authorization: Bearer {access_token}
X-Tenant-Id: {tenantId}
X-Request-Timestamp: {unixSeconds}
X-Content-SHA256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Request-Signature: {jws}
{
"accountId": "6ff57bc1-e4a9-403b-be62-42378b8aafd7",
"total": 18254.17,
"locked": 500.00,
"available": 17754.17,
"currency": "BRL",
"updatedAt": "2026-02-05T22:00:00Z"
}
  1. PIX out (only after verify + balance succeed)
POST https://client.api.corpx.com/v1/accounts/{accountId}/pix/out
Authorization: Bearer {access_token}
X-Tenant-Id: {tenantId}
Idempotency-Key: {uuid}
X-Request-Timestamp: {unixSeconds}
X-Content-SHA256: {sha256(jsonBody)}
X-Request-Signature: {jws}
Content-Type: application/json
{
"amount": 100.00,
"keyType": "CPF",
"key": "12345678901",
"description": "order-12345",
"identifier": "order-12345"
}

Treat 200 COMPLETED as done. 202 / 207 are indeterminate — wait for pix.out.completed / failed / timeout or look up the payment. 422 is a business rejection (errorCode).

  1. Webhook (create once, with accountId):
{
"url": "https://example.com/webhooks/account",
"accountId": "{accountId}",
"authType": "HMAC",
"secret": "{webhookSecret}",
"eventTypes": ["pix.in.completed", "pix.out.completed", "pix.out.failed", "qrcode.paid"]
}

Verify X-Signature = base64(HMAC_SHA256(secret, raw_body)) on the raw bytes. Reply 2xx quickly.

Offline test vector

Public key, canonical string, content SHA-256 and a valid JWS are in request-signing § test vector. kid = cfe8291443215153e11b76a7a533dd3c. Use it to unit-test your signer before calling the API.

Error decision table

errorCodeAction
signed_host_requiredSwitch host to client.api.corpx.com
signature_required / request_signature_requiredAdd the three signing headers
request_signature_invalidDiff canonicalString from /signature/verify; check LF vs CRLF and R||S vs DER
request_timestamp_skewSync NTP; timestamp is unix seconds
unknown_kidSign with an active kid (new keys wait 18h)
body_hash_mismatchHash the exact bytes on the wire
credential_not_yet_activeWait for activeFrom
ip_not_allowedCredential allowlist; adding a CIDR waits 18h
account_id_requiredSend accountId on webhook create
forbiddenWrong accountId, or a BaaS-only route (PIN, lock write, accreditation)
cashout_locked / cashout_outside_hours / cashout_source_ip_not_allowedAccount-holder lock in the bank UI — not a signing bug
insufficient_funds / pix_key_not_found / partner_rejectedBusiness; do not retry blindly

Human-facing detail: errors.