Skip to main content

Migrate from v1 — full reference

For the 2-minute executive summary, see Migrate from v1 — quick version.

This document exists for devs auditing the migration, ops team, and edge cases. It lists ~75 integrator-facing routes and how each one behaves on v2.


1. Authentication

Before (v1)

TOKEN_URL="https://corpx-{env}.auth.sa-east-1.amazoncognito.com/oauth2/token"
curl -X POST "$TOKEN_URL" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=api/full"

Now (v2)

TOKEN_URL="https://auth.api.corpx.com/oauth2/token"
curl -X POST "$TOKEN_URL" \
-u "$CLIENT_ID:$CLIENT_SECRET" \
-d "grant_type=client_credentials&scope=api2/read api2/write"

Differences:

  • Token URL changes (goes through our CloudFront).
  • scope: api/fullapi2/read api2/write.
  • JWT TTL: 1h (same as v1).
  • Idempotency-Key: optional on v2 (auto-generated if absent; we recommend you keep sending yours for retry control).
  • X-Tenant-Id: required on all /v1/* requests (except GET /v1/me and health). Must match the account's tenant when accountId is in the path.

2. Deprecated endpoints (still work until 2026-11-21)

4 paths that existed on v1 and weren't kept in the canonical v2 shape have been re-enabled as deprecated aliases. They keep responding normally — they just attach 3 warning headers to the response:

Deprecation: true
Sunset: Sat, 21 Nov 2026 00:00:00 GMT
Link: </v1/accounts/.../new-path>; rel="successor-version"

These headers follow RFC 8594 (Deprecation) and RFC 9745 (Sunset). Clients can detect them automatically to alert the team.

Planned sunset: 2026-11-21. After that date these routes start returning 410 Gone.

Deprecated route (still works)Canonical replacement
GET /v1/accounts/{id}/pix/payments/{paymentId}GET /v1/accounts/{id}/pix/payments/lookup?identifier=...
GET /v1/accounts/{id}/payments/{paymentId} (alias without /pix/)GET /v1/accounts/{id}/pix/payments/lookup?identifier=...
GET /v1/accounts/{id}/pix/qr-code (without /lookup)GET /v1/accounts/{id}/pix/qr-code/lookup?identifier=...
POST /v1/accounts/{id}/pix/out/qrcode (without hyphen)POST /v1/accounts/{id}/pix/out/qr-code

These routes no longer appear in the OpenAPI or the Postman collection — they are documented here only to give you time to migrate. New integrations should use the canonical replacements directly.


3. Removed endpoints (7 routes — return 404)

v1 route (removed)What to do
POST /v1/webhooks/replayUse statement polling in a short window
GET /v1/integrator/webhooksUse GET /v1/webhooks
GET /v1/integrator/webhooks/{id}/deliveriesNo replacement — contact support if you depend on it
POST /v1/integrator/events/replayNo replacement
POST /v1/integrator/events/replay/batchNo replacement
POST /v1/accounts/{id}/pix/med/{medId}/sendUse /decide (but MED is at 503 today)
POST /v1/accounts/{id}/pix/med/{medId}/responseUse /answer (but MED is at 503 today)

GET /v1/health still exists on v2. A GET /health alias (without /v1) was added for convenience of external health checks — no action needed.


4. Endpoints with behavioral changes

Same path, same method, but different behavior or JSON shape.

4.1 Statement — cache → live (and reduced payer/payee)

GET /v1/accounts/{id}/statement

v1: read from a local cache (DynamoDB) populated by webhooks. Latency ~50ms; enriched items with tariff_ref and translated labels. Rows had full payer.{bankCode,bankIspb,branch,account,pixKey,...} and beneficiary.{...} because they were assembled from webhooks (which carry every banking field of the counterparty).

v2: calls the settlement bank in real time. Latency ~500ms-1.5s; no derived tariff_ref (fees appear as their own line in the statement); new header X-Source: live. Max window per query: 31 days.

What changed in the row shape

v2 standardized the counterparty object to name + document plus the bank info of our side (settlement bank = MT Bank). The remaining banking fields of the counterparty (branch, account, pixKey, etc.) are no longer present in statement rows because the settlement bank's statement endpoint does not expose them.

v1 (legacy, served from webhook cache):

{
"amount": -100.00,
"direction": "OUT",
"endToEndId": "E...",
"payer": { "name": "...", "document": "...", "bankCode": "681", "bankIspb": "50871921", "branch": "...", "account": "...", "pixKey": "..." },
"beneficiary":{ "name": "...", "document": "...", "bankCode": "001", "bankIspb": "00000000", "branch": "...", "account": "...", "pixKey": "..." }
}

v2 (live, served from the settlement bank):

{
"amount": -100.00,
"direction": "OUT",
"endToEndId": "E...",
"payer": { "name": "MY COMPANY LTDA", "document": "12345678000190", "bankCode": "681", "bankIspb": "50871921", "bankName": "MT Instituição de Pagamentos" },
"payee": { "name": "SUPPLIER XYZ", "document": "98765432000110" },
"counterParty": { "name": "SUPPLIER XYZ", "document": "98765432000110" }
}

Notes on the v2 shape:

  • The payer / payee pair is built from the row direction:
    • direction=IN: payer = counterparty (who paid us); payee = our account;
    • direction=OUT: payer = our account; payee = counterparty (who we paid).
  • The self side (our account) always brings name (holder), document (CPF/CNPJ) and the settlement bank identifiers (bankCode, bankIspb, bankName).
  • The counterparty side always brings only name + document. Optional fields (bankCode, bankIspb, branch, account, pixKey, accountType, bankName) are omitted when empty — v2's partyToDTO does not emit keys with empty strings.
  • counterParty (extra object with name + document of the counterparty) is kept to make lookup easier without inspecting direction.
  • Removed authorizationCode field (the settlement bank's API does not expose it on the statement; it was always empty on v1 when served from the live fallback too).

Where to recover the full counterparty bank payload

If your integration needs branch/account/pixKey/bankCode for the counterparty, use one of the alternatives below (all return the enriched payload, since they come from specific PIX endpoints, not from the statement):

  • Outbound webhook: pix.in.completed, pix.out.completed, pix.refund.completed, qrcode.paid — the CorpX envelope includes payer / payee with all bank fields.
  • Single payment lookup: GET /v1/accounts/{id}/pix/payments/lookup?endToEndId=... — returns the enriched transaction object (same as the webhook).
  • QR Code: GET /v1/accounts/{id}/pix/qr-code/lookup?identifier=... — for paid QRs, includes payer + payment with endToEnd.

If your integration relied on aggressive statement polling, prefer webhooks:

  • pix.in.received for received PIX
  • pix.out.confirmed for completed PIX out
  • boleto.paid / boleto.failed
  • qrcode.paid

The same effects apply to the other endpoints that used to read from the same cache:

  • GET /v1/accounts/{id}/pix/transactions
  • GET /v1/accounts/{id}/pix/payments
  • GET /v1/accounts/{id}/payments (alias)

All now query the settlement bank in real time, with no local cache.

4.2 MED — at 503 during migration

All /v1/accounts/{id}/pix/med/* endpoints return 503 service_temporarily_unavailable. Reactivation planned for v2.x. If you have active MEDs at cutover time, our team will reach out individually.

med.* webhooks are also suspended during this window — no dispute events will be emitted. You don't need to remove the subscription; when the module returns, events resume automatically.

4.3 Boleto — now active

v1 returned 503 on all boleto endpoints (POST .../boleto/preview, POST .../boleto/pay, GET .../boleto/payments/{id}). v2 is active. See Cash Out guide.

4.4 PIX out — sync vs async (not “everything async”)

v2 keeps the same split as v1:

RouteModeHTTP response
POST .../pix/outsyncWaits on the workflow up to ~25s. If it finishes in time: 200 with final status (APPROVED, FAILED422, etc.) and endToEndId when available. On timeout: 202 with status: PENDING + warning — then use lookup/webhooks.
POST .../pix/out/qr-codesyncSame as /pix/out (~25s).
POST .../pix/out/bank-accountsyncSame.
POST .../pix/out/refundsyncSame.
POST .../pix/out/asyncasyncImmediate 202 with {paymentId, workflowId, idempotencyKey, identifier} — final outcome via webhook or GET .../pix/payments/lookup?identifier=....
POST .../pix/out/bank-account/asyncasyncImmediate 202 (same as /async).
POST .../pix/out/bigpix (+ bank-account/bigpix)asyncImmediate 202 (batch; server-side chunking unchanged).

What changed under the hood (no forced migration to async): orchestration uses Temporal, but callers of POST .../pix/out without /async stay on the synchronous path. Move to /async only for high throughput or to avoid holding the HTTP connection for ~25s.

Idempotency: retries with the same Idempotency-Key reuse the same paymentId/workflow while still in flight; after FAILED, a new key may start a new attempt (see Cash Out).

4.5 X-CF-Origin-Verify

v2 only accepts requests coming from CloudFront. Hitting execute-api directly returns 403. Always use https://tenant.api.corpx.com/v1.

4.6 Temporarily suspended webhooks — fee.*, med.*, edi.*

During the migration window, 3 event families are not emitted:

FamilyStatusHow to still see the data
fee.* (fee.charged, fee.refunded, etc.)suspendedFees appear as standalone lines in the statement (GET /v1/accounts/{id}/statement). Look for type=internal-transfer with identifier=fee-{slug}-{operationReferenceId} to CORPX_FEE_ACCOUNT_ID.
med.* (med.opened, med.answered, etc.)suspendedThe entire MED module is offline (see §4.2).
edi.* (CNAB/EDI batch file events)suspendedFiles keep being generated, but webhook notification is paused. Use the CNAB batches GET to track.

What this means for you:

  • Subscriptions to these events can remain registered — when they return, they resume automatically.
  • If your reconciliation depended on these events, switch temporarily to statement polling (queries to the settlement bank are real-time, no cache lag).

Planned reactivation in phases:

  • fee.* — next minor after the no-cache refactor stabilizes.
  • med.* — along with MED module reactivation on v2.x.
  • edi.* — TBD; depends on the new batches pipeline.

4.7 Automatic transaction ↔ fee reconciliation — suspended

v1: the transaction object included a fee: { amount, ... } field that paired each PIX/boleto with its processing fee.

v2: that field was temporarily removed. Reconciliation is done client-side by cross-referencing the identifier:

  • Main transaction: identifier = <your identifier> (or auto-generated)
  • Matching fee: identifier = fee-{slug}-{operationReferenceId} in the same time window, debited from CORPX_FEE_ACCOUNT_ID

How to reconcile via statement:

GET /v1/accounts/{accountId}/statement?startDate=2026-05-21&endDate=2026-05-21

Filter items where identifier starts with fee- and cross-reference with the main transaction via the embedded operationReferenceId.

When the no-cache refactor stabilizes, the fee field returns on the transaction object. No payload change planned for that return — just the field reappearing.

4.8 Internal transfer by-bank-account — optional extra fields

POST /v1/accounts/{id}/transfers/internal/by-bank-account

v2 accepts two extra optional fields: holderDocument and holderName. The current settlement bank needs these; when the client doesn't send them, the backend does the lookup automatically. Not breaking — clients that keep sending only {branch, accountNumber, value, ...} work normally.

4.9 PIX QR-code — flattened response (breaking JSON)

Response parser breaking change

In the table (§6), these endpoints are marked body-diff, not unchanged or generic changed: the path is the same, but the success JSON changed. Code that uses response.data.payload or reads data.location breaks until you update the parser — including POST .../pix/qr-code/dynamic.

Affects the 5 QR-code endpoints:

  • POST /v1/accounts/{id}/pix/qr-code/dynamic
  • POST /v1/accounts/{id}/pix/qr-code/static
  • GET /v1/accounts/{id}/pix/qr-code/lookup (and the deprecated alias GET /v1/accounts/{id}/pix/qr-code)
  • DELETE /v1/accounts/{id}/pix/qr-code

v1: Finaya-style envelope:

{
"statusCode": 200,
"title": "...",
"type": "...",
"message": "...",
"data": {
"txid": "ABC123",
"payload": "00020126...",
"location": "https://qr.mtbank.com.br/cob/...",
"...": "..."
}
}

v2: flat canonical shape, no envelope:

{
"txid": "ABC123",
"emv": "00020126...",
"type": "dynamic",
"status": "ACTIVE",
"value": 12.34,
"message": "Charge X",
"identifier": "abc123",
"expiresAt": "2026-12-31T23:59:59Z",
"createdAt": "2026-05-21T20:00:00Z"
}

Field mapping:

v1 (envelope)v2 (flat)Notes
data.txidtxidunchanged
data.payloademvsame BRcode/EMV string
data.location(removed)render the QR from emv on the client
statusCode, title, type, message (envelope)(removed)replaced by canonical fields type, status, value, identifier, expiresAt, createdAt

HTTP status code: v2 returns 201 Created on creation (POST). GET stays 200.

How to migrate (minimum):

- const emv = response.data.payload;
+ const emv = response.emv;

- const qrUrl = response.data.location;
+ // No native equivalent. Render the EMV with qrcode-svg / qrcode.js,
+ // or host it on your own CDN if you need a public URL.

If removing location is a blocker for you, talk to support — we can evaluate reintroducing the field by generating a CDN URL with the cached EMV.


5. New endpoints

EndpointPurpose
GET /v1/meIdentity of the authenticated client_id (useful for debug)
GET /v1/transactions/{id}/timelineDetailed timeline of a transaction (no accountId in the path)
GET /v1/accounts/{id}/pix/out/bigpix/{batchId}Aggregated BigPix batch status
GET /healthAlias of /v1/health (without the /v1 prefix)

6. Full table

Status legend:

  • unchanged: same path and same success response JSON. Your parser stays the same.
  • body-diff: same path; different success response JSON (breaking). E.g. QR-code without { data: {...} } envelope — see §4.9.
  • changed: same path; behavior differs (live, latency, 503→200). Success JSON is usually compatible; read the note.
  • deprecated: still works until 2026-11-21, with Deprecation: true header. Migrate to the canonical replacement before sunset.
  • removed: route existed only on v1; returns 404 on v2.
  • added: new route; only exists on v2.
Endpointv2 statusNote
GET /v1/healthunchangedAlso accepts GET /health
GET /v1/accreditations/pfunchanged
POST /v1/accreditations/pfunchangedBody with simplified tier for CNPJ may require Agreement
PUT /v1/accreditations/pfunchanged
GET /v1/accreditations/pjunchanged
POST /v1/accreditations/pjunchangedSame
PUT /v1/accreditations/pjunchanged
GET /v1/accounts/{id}/balanceunchanged
GET /v1/accounts/{id}/statementchangedLive (no cache); see §4.1
GET /v1/accounts/{id}/transactions/timelineunchanged
GET /v1/transactions/{id}/timelineaddedNew
POST /v1/accounts/{id}/locked-balance/lockremoved (2026-05)v2 no longer has local hold. /balance.locked reflects only MT blocked.
POST /v1/accounts/{id}/locked-balance/unlockremoved (2026-05)idem
GET /v1/accounts/{id}/pix/limitsunchanged
PATCH /v1/accounts/{id}/pix/limitsunchanged
POST /v1/accounts/{id}/pix/outunchangedSync (~25s): 200/422 when done; 202+warning on timeout. §4.4
POST /v1/accounts/{id}/pix/out/asyncunchangedAsync: immediate 202. §4.4
POST /v1/accounts/{id}/pix/out/bigpixunchangedSingle body; server-side chunking; immediate 202 (async). §4.4
GET /v1/accounts/{id}/pix/out/bigpix/{batchId}addedAggregated batch status
POST /v1/accounts/{id}/pix/out/bank-accountunchangedSync (~25s). §4.4
POST /v1/accounts/{id}/pix/out/bank-account/asyncunchangedAsync: immediate 202. §4.4
POST /v1/accounts/{id}/pix/out/bank-account/bigpixunchangedAsync: immediate 202 (batch). §4.4
POST /v1/accounts/{id}/pix/out/qr-codeunchangedSync (~25s), same as /pix/out. §4.4
POST /v1/accounts/{id}/pix/out/qr-code/decodeunchanged
POST /v1/accounts/{id}/pix/out/qrcode (alias, no hyphen)deprecatedSunset 2026-11-21. Use /pix/out/qr-code
POST /v1/accounts/{id}/pix/out/refundunchangedSync (~25s). §4.4
GET /v1/accounts/{id}/pix/transactionschangedLive
GET /v1/accounts/{id}/pix/paymentschangedLive
GET /v1/accounts/{id}/pix/payments/{paymentId}deprecatedSunset 2026-11-21. Use /pix/payments/lookup?identifier=
GET /v1/accounts/{id}/pix/payments/lookupunchanged
GET /v1/accounts/{id}/payments (alias)changedLive
GET /v1/accounts/{id}/payments/{paymentId} (alias)deprecatedSunset 2026-11-21. Use /pix/payments/lookup?identifier=
POST /v1/accounts/{id}/boleto/previewchanged503 → 200 (boleto active on v2)
POST /v1/accounts/{id}/boleto/paychanged503 → 200
GET /v1/accounts/{id}/boleto/payments/{paymentId}changed503 → 200
POST /v1/accounts/{id}/pix/qr-code/staticbody-diffBreaking: no envelope; data.payloademv; no data.location. §4.9
POST /v1/accounts/{id}/pix/qr-code/dynamicbody-diffBreaking: no envelope; data.payloademv; no data.location. §4.9
GET /v1/accounts/{id}/pix/qr-code (without /lookup)deprecatedSunset 2026-11-21. Use /pix/qr-code/lookup?identifier=. Response also body-diff (§4.9)
GET /v1/accounts/{id}/pix/qr-code/lookupbody-diffBreaking: same flat shape as POST. §4.9
DELETE /v1/accounts/{id}/pix/qr-codebody-diffBreaking: flat response. §4.9
GET /v1/accounts/{id}/pix/qr-codesunchangedPaginated list — not the same contract as POST dynamic (§4.9)
GET /v1/accounts/{id}/pix/qr-codes/statsunchanged
GET /v1/accounts/{id}/pix/keysunchanged
POST /v1/accounts/{id}/pix/keysunchanged
DELETE /v1/accounts/{id}/pix/keys/{pixKey}unchanged
GET /v1/accounts/{id}/pix/key/{pixKey}unchangedDICT lookup
GET /v1/accounts/{id}/pix/medchanged503 stub; see §4.2
POST /v1/accounts/{id}/pix/med/{medId}/answerchanged503 stub
POST /v1/accounts/{id}/pix/med/{medId}/decidechanged503 stub
POST /v1/accounts/{id}/pix/med/{medId}/evidence/upload-urlchanged503 stub
POST /v1/accounts/{id}/pix/med/{medId}/evidence/addchanged503 stub
POST /v1/accounts/{id}/pix/med/{medId}/evidence/downloadchanged503 stub
POST /v1/accounts/{id}/pix/med/{medId}/sendremovedUse /decide (at 503 today)
POST /v1/accounts/{id}/pix/med/{id}/responseremovedUse /answer (at 503 today)
POST /v1/accounts/{id}/transfers/internalunchanged
POST /v1/accounts/{id}/transfers/internal/by-bank-accountunchangedAccepts optional extras (holderDocument, holderName); see §4.8
POST /v1/accounts/{id}/transfers/internal/by-documentunchanged
GET /v1/accounts/{id}/transfers/internal/lookup/{document}unchanged
POST /v1/accounts/{id}/exportsunchanged
GET /v1/accounts/{id}/exportsunchanged
GET /v1/accounts/{id}/exports/{exportId}/downloadunchanged
GET /v1/meaddedclient_id debug
GET /v1/webhooksunchanged
POST /v1/webhooksunchanged
PUT /v1/webhooks/{subscriptionId}unchanged
DELETE /v1/webhooks/{subscriptionId}unchanged
GET /v1/webhooks/eventsunchangedCatalog
POST /v1/webhooks/replayremovedRemoved
GET /v1/integrator/webhooksremovedUse GET /v1/webhooks
GET /v1/integrator/webhooks/{id}/deliveriesremovedNo replacement
POST /v1/integrator/events/replayremovedNo replacement
POST /v1/integrator/events/replay/batchremovedNo replacement
GET /v1/security/ip-allowlistunchanged
PUT /v1/security/ip-allowlistunchanged
GET /v1/security/ip-allowlist/auditunchanged

Summary: 46 unchanged, 5 body-diff (PIX QR-code — flat JSON, §4.9), 12 changed (live statement, MED stubs, boleto 503→200, etc.), 4 deprecated (aliases, sunset 2026-11-21), 7 removed (404 on v2), 3 added.


7. Account IDs preserved

The account_id you use in all paths and webhooks are the same before and after the cutover. You do not need to update references stored in your system. They remain UUIDs.

What changes internally (transparent to you):

  • Settlement-bank identifiers are new — you never saw this field.
  • Settlement-bank authentication mechanism changed — internal handler.

8. Full FAQ

Does my webhook URL need to change? No.

Does my HMAC secret change? No.

Do account IDs change? No.

Can I go back to v1 if something goes wrong? During the first 4h after cutover, yes — we have a rehearsed rollback. After that, the old infrastructure is gradually decommissioned.

For how long is the old backoffice available? 30 days after cutover.

Does high-frequency statement polling still work? It does, but with higher latency (~1s vs ~50ms of the old cache) and a 31-day max window per query. For changes, prefer webhooks.

Are there new rate limits? We keep the same per-tenant limits as v1. If you hit them, you get 429 Too Many Requests with Retry-After.

Will the fee field return on the transaction object? Yes, in the next minor after stabilization. No payload change — just the field reappearing.

Will the fee.* / med.* / edi.* webhooks return? Yes, in phases: fee.* on the next minor; med.* along with MED module reactivation on v2.x; edi.* TBD.


Support

Contact: suporte-api@corpx.com — response within 1h on business hours.