v2.75.2 — Account number on the tenant account list

  • GET /v1/backoffice/tenants/{tenantId}/accounts now returns accountNumber on each item. It is the local override (accounts.account_number); when none is stored the field is null. IB and the backoffice no longer need a GET /bank-account per row.

v2.76.0 — Ownership OTP for email and phone PIX keys

  • POST /v1/accounts/{accountId}/pix/keys with email or phone no longer returns 422. The facade sends an OTP (email or SMS) and responds 202 pending_verification with challengeId and a masked destination. The partner is called only after the code is confirmed.
  • POST /v1/accounts/{accountId}/pix/keys/verify confirms the code and registers the key (201). Errors: code_invalid, code_expired, challenge_locked, challenge_not_found.
  • POST /v1/accounts/{accountId}/pix/keys/verify/resend resends (60s cooldown, 3 sends max). A delivery failure returns 503 otp_send_failed and the challenge is not usable.
  • CPF, CNPJ and random keys still return 201 immediately.

v2.77.0 — Advanced search with `order=desc` and time-of-day bounds

  • GET /v1/accounts/{accountId}/statement/advanced gains order=desc for accounts with thousands of entries a day that only want the most recent ones: the scan starts from the end and stops once it crosses occurredAfter. No cursor in this mode (400 invalid_cursor); continuation is by time, nextOccurredBeforeoccurredBefore, inclusive bound (deduplicate by id).
  • New occurredAfter / occurredBefore filters (RFC 3339 with offset, inclusive) in either order. They only shorten the scan when they are the boundary in the scan direction; otherwise they are just filters.
  • order in the response now echoes asc or desc. Behaviour without order is unchanged.

v2.74.0 — Per-account credentials, request signing, PIN and cashout locks

  • New delegated credential: one credential per account, signed. A credential holding the credentials.delegate scope issues child credentials bound to one account, with scopes that are always a subset of its own, and both publicKeyPem and allowedIps (1 to 20 CIDRs) required. Issuing returns status: "pending" and an activeFrom 18h in the future — until then the credential answers 403 credential_not_yet_active. Revoking is immediate, including during the grace period. See Authentication.
  • Delegated credentials call https://client.api.corpx.com and sign every request with a detached JWS (ES256 or PS256) over METHOD\nPATH?QUERY\nTIMESTAMP\nIDEMPOTENCY_KEY\nX-Content-SHA256, plus the X-Request-Timestamp (300s tolerance), X-Content-SHA256 and X-Request-Signature headers. On the old host they get 403 signed_host_required. New guide: Request Signing.
  • POST /v1/security/signature/verify returns the canonical string we expect, the body hash and the verification result, so you can get signing right before your first transaction. Always 200, never creates anything.
  • Public keys and IP allowlist per credential, at GET|POST .../credentials/{clientId}/public-keys, DELETE .../public-keys/{kid} and GET|PUT .../credentials/{clientId}/ip-allowlist. Adding a key or adding an IP waits 18h (pendingUntil); retiring a key or removing an IP is immediate. For tenants that have delegated credentials the tenant IP allowlist is derived from those credentials, and the manual PUT /v1/security/ip-allowlist now answers 409 ip_allowlist_derived.
  • Per-account webhook subscriptions. POST /v1/webhooks accepts accountId: the subscription then receives only that account’s events. The field cannot be edited afterwards, and a credential restricted to specific accounts must provide it (422 account_id_required). See Per-account subscriptions.
  • Cashout locks configured by the account holder at GET|PUT /v1/accounts/{accountId}/security/locks: cashoutBlocked, cashoutHours and cashoutSourceIps. Tightening applies immediately; loosening waits 6 hours (pendingEffectiveAt), field by field. DELETE .../locks/pending cancels the scheduled change and POST .../locks/pending/approve brings it forward with the PIN. A blocked cashout returns 423 cashout_locked, 403 cashout_outside_hours or 403 cashout_source_ip_not_allowed.
  • Per-operator transaction PIN at PUT /v1/accounts/{accountId}/security/pin, DELETE .../security/pin/{document}, POST .../security/pin/verify and GET .../security/pin/status. When the credential requires a PIN, cashout routes start requiring X-Acting-Document and X-Transaction-Pin (428 pin_required). 3 wrong attempts lock for 15 minutes (429), 6 lock until reset (423). PIN is 6 to 12 digits, no repetition, sequence or birth date (422 weak_pin).
  • GET /v1/accounts/{accountId}/shared-access lists the tenants operating the same bank account, with displayName, status, since and isCurrent. Public metadata only.
  • Nothing changes for existing integrations. Current credentials keep calling https://tenant.api.corpx.com with no signature; accounts with no lock and no PIN behave as before; webhook subscriptions without accountId keep receiving every account’s events.

v2.74.1 — Docs: BaaS and Internet banking

  • Two sections on the site. Navbar BaaS (tenant.api.corpx.com) and Internet banking (client.api.corpx.com). Home no longer drops you on the tenant Quick Start.
  • llms.txt / llms-full.txt route the two audiences. Do not assume a single base URL. Filter the OpenAPI by x-audience (baas, ib, or both). Each summary carries the prefix [BaaS], [IB] or [BaaS · IB].
  • The API contract does not change. Paths, PIN and locks stay those of 2.74.0.

v2.75.0 — Local limits in the MT shape

  • GET /v1/accounts/{accountId}/limits/available is new. It returns the four MT windows (singleTransfer, daytime, nighttime, monthly) with usedBrl/availableBrl for PIX, TED, boleto and internal transfers. GET /v1/accounts/{accountId}/pix/limits does not change: it is still the partner ceiling list, with no used field.
  • TED, boleto and internal only get a local ceiling after staff writes one. Without a ceiling, behavior matches today (fail-open). Internal transfers still consult MySQL (limites_cliente); if a teto exists there and the payment fits, it authorizes without a ledger hold.
  • Per-operator limits at GET|PUT|DELETE /v1/accounts/{accountId}/limits/operators/{document}: the owner slices the account ceiling (available = min). Tightening is immediate; loosening waits 6 hours. PIX only reserves locally when an operator ceiling exists — the partner still enforces the account PIX ceiling.
  • A PIX refusal with Limite restante: R$ X aligns used for the current window. There is no periodic sync and the GET does not poll the partner.

v2.75.1 — Staff writes a local PIX ceiling

  • PATCH /v1/backoffice/accounts/{accountId}/limits accepts pixOut in the same shape as the other operations. Without a local ceiling, PIX stays fail-open on the ledger; the partner still enforces the account cap.
  • PIX Reserve evaluates min(local account, operator) when an account ceiling exists. GET /pix/limits does not change.
  • used is now an atomic per-window counter (account_limit_usage), not a SUM of holds. Concurrent PIX on the same account queue on the window row and cannot overshoot. Holds remain for the saga and idempotency only.

v2.70.0 — Company documents count only after the file exists

  • POST /v1/accreditations/{id}/documents now requires sizeBytes (the PDF’s actual size, 20 MB max). It is part of the uploadUrl signature: the PUT must send exactly that many bytes.
  • The POST only reserves the slot. GET keeps the kind in missingDocumentKinds and documents[].deliveryStatus stays pending_upload until the object exists in the KYC archive with size > 0 (and passes the scan when the pipeline is on). uploadedAt is only set when deliveryStatus is delivered.
  • kind=other is capped at 10 files per accreditation (422 too_many_files). A PDF over 20 MB returns 413 file_too_large.
  • Approving without the object requires an override. An empty slot returns 409 documents_incomplete; forceIncompleteDocuments + reason writes the documentsOverride* audit trail.

v2.71.0 — Risk-checked acceptance

  • BYO acceptance page: if the risk check is unavailable, acceptance returns 503 risk_check_unavailable. It is retryable — the holder tries again in a moment.

v2.72.0 — Idempotency without header and biometry evidence

  • PIX out and boleto payments sent without Idempotency-Key are now deduplicated by content. Two identical requests (same account, amount and destination) reuse the same payment. Keep sending the header: a payment you intend to repeat still needs its own key.
  • biometry.evidenceId is accepted on an accreditation only after content verification. While the file is in quarantine or rejected, the POST returns 422 evidence_not_approved.
  • Consent page: identity not yet approved or expired returns 409 identity_not_approved / identity_stale and the journey stays pending — the holder recaptures via the same link.
  • POST /v1/accounts/{accountId}/transfers/internal/by-document returns 503 beneficiary_check_unavailable when the destination cannot be confirmed. Retry or use /transfers/internal/by-bank-account.

v2.68.1 — PIX-out webhook docs match the payload we send

  • pix.out.completed and pix.out.failed docs now describe the HTTP body we actually POST. The envelope is unchanged (id, type, occurredAt, schemaVersion, environment, tenantId, accountId, data). Inside data we always send paymentId, status, currency ("BRL"), description, originalTransactionId, initiatedAt and completedAt (RFC 3339 with fractional seconds), plus endToEnd, identifier and amount.
  • payer is present only when the settlement bank webhook included the origin account. payee may include bankIspb and pixKey. We do not send reconciliationId on these events.
  • The “webhook delivered” JSON in the portal is only the data object, not the full POST. The outbound type is pix.out.completed — not the timeline event pix_out.confirmed.

v2.68.2 — Remaining webhook docs match the HTTP body

  • pix.in.completed, qrcode.paid / expired / cancelled, pix.refund.completed / failed and boleto.paid / failed docs now describe the payload we actually POST. Older examples invented reconciliationId, originalEndToEnd/refundEndToEnd on refunds we initiate, txid/expiredAt on QR events, and an object-shaped error on boleto.
  • pix.refund.completed uses the same shape as pix.out.completed. The refund E2E is endToEnd; the original transaction is originalTransactionId. originalEndToEnd / refundEndToEnd exist only on pix.refund.received.
  • The portal “webhook delivered” JSON is still only data, not the full POST.

v2.69.0 — EMAIL and PHONE PIX key registration temporarily unavailable

  • POST /v1/accounts/{accountId}/pix/keys rejects keyType email and phone. The response is 422 key_type_temporarily_unavailable. CPF, CNPJ, and random (random) keys can still be registered.
  • Listing and deleting those key types is unchanged. Existing email or phone keys remain visible and removable. PIX out to an email or phone key is also unchanged.

v2.69.1 — Consent 403 and masked internal lookup

  • POST /v1/accreditations/consent/{cst} without a session cookie returns 403 session_invalid and does not record a decision. The journey stays in pending_consent.
  • POST /v1/accreditations/consent/{cst}/start returns 409 when the journey is not pending or a capture is already in progress.
  • consent_facial_failed is only an identity-verification failure. Risk or device refusal is still castle_deny. An explicit holder refusal is still consent_declined.
  • GET /v1/accounts/{accountId}/transfers/internal/lookup/{document} now returns maskedName and the same value in holderName. The first name is kept in full; remaining tokens become first letter + ***. holderDocument is fully masked (***.***.***-** / CNPJ equivalent) — no digits in the body.
  • branch, accountNumber, and accountStatus stay in the JSON. POST by-document is unchanged.

v2.69.2 — Dynamic `qrcode.paid` includes `receivedAt`

  • Dynamic qrcode.paid includes receivedAt in RFC 3339 UTC, the same instant as that payment’s pix.in.completed.

v2.67.0 — Statement export as Excel

  • POST /v1/accounts/{accountId}/exports now accepts format: "xlsx". The Excel file uses the same columns as the CSV (including pixKey). If one sheet fills, the rest continues on Statement_2, Statement_3, …
  • Exports no longer truncate the statement. PDF no longer stops at 10,000 rows; CSV no longer stops at 2 million. PDF still only clamps the date window (from 21/05/2026 up to 24h ago).

v2.66.6 — Fee and commission labels on internal transfers

  • An internal transfer to the operational fee accounts now shows as TARIFA on the statement, the entry detail, and CSV/PDF export, with the counterparty CNPJ masked.
  • When the amount comes back from those accounts, the description is ESTORNO DE TARIFA.
  • A commission payout (description or identifier containing “comissão”) shows as REPASSE DE COMISSÃO on credit and ESTORNO DE REPASSE DE COMISSÃO on debit. It does not become a fee.* event.
  • The fee.charged and fee.refunded webhooks use the same TARIFA and ESTORNO DE TARIFA descriptions.

v2.66.5 — Paid dynamic QR now includes e2e, payer, and `pix.in.completed`

  • qrcode.paid for a dynamic QR now includes endToEnd, payer, and receivedAt. On some payments those fields went out empty even though the credit had already landed.
  • pix.in.completed is now sent with those same payments. The documented pair (pix.in.completed + qrcode.paid) no longer drops the credit event.

v2.66.2 — Internal transfer identifier aligned

  • Fixed divergent identifiers on internal-transfer events in some scenarios.

v2.66.3 — Statement export includes the PIX key

  • CSV and PDF statement exports now include pixKey, the same field as on each statement item: on a credit, the key of this account that received the money; on a debit, the destination key. In the CSV the column is appended at the end, so existing columns stay in place.