Migrate from v1 — full reference
Migrate from v1 — full reference
Endpoint-by-endpoint audit of the v1 → v2 migration.
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)
Now (v2)
Differences:
- Token URL changes (goes through our CloudFront).
scope:api/full→api2/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 (exceptGET /v1/meand health). Must match the account’s tenant whenaccountIdis 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:
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.
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 (8 routes — return 404)
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):
v2 (live, served from the settlement bank):
Notes on the v2 shape:
- The
payer/payeepair is built from the rowdirection: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’spartyToDTOdoes not emit keys with empty strings. counterParty(extra object withname+documentof the counterparty) is kept to make lookup easier without inspectingdirection.- Removed
authorizationCodefield (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 includespayer/payeewith 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, includespayer+paymentwithendToEnd.
If your integration relied on aggressive statement polling, prefer webhooks:
pix.in.receivedfor received PIXpix.out.confirmedfor completed PIX outboleto.paid/boleto.failedqrcode.paid
The same effects apply to the other endpoints that used to read from the same cache:
GET /v1/accounts/{id}/pix/transactionsGET /v1/accounts/{id}/pix/paymentsGET /v1/accounts/{id}/payments(alias)
All now query the settlement bank in real time, with no local cache.
4.2 MED — active again, with a different contract from v1
As of v2.58.0 the MED routes answer: GET /v1/accounts/{id}/pix/med, POST .../answer and the three evidence routes. Four differences from v1, all detailed in the Disputes guide:
- The list comes from the webhooks. The scheme offers no dispute lookup, so the
GETreturns whatpix.med.opened/pix.med.updatedrecorded. Subscribing to those events is now part of integrating MED. - There is no dispute status in the API. With no lookup at the settlement bank, the stored state is not verifiable, so the
GETneither returnsstatusnor accepts a filter on it. What comes out isansweredandclientAnswerDeadline. To track state, accumulate thepix.med.updatedevents — theirstatusis the one from the instant of the event. answerchanged shape and effect:result(AGREE/DISAGREE) +reason, instead ofanswer: "CONCORDO"/"DISCORDO"withevidences. It is sent only once and it records your defense, which travels with the attachments to the team that conducts the dispute — it does not go to the settlement bank.- There is no decision through the API. Refunding the disputed PIX, holding balance or refusing the refund are not operations of this API.
POST .../sendandPOST .../decidedo not exist.
Evidence works again, with a contract of its own: evidence/upload-url (presign), evidence/add (registration) and GET evidence/download (listing with a signed URL). Limits: PDF/JPEG/PNG/WebP/TXT/CSV, 5 MB per file, 6 MB and 10 files per dispute, all before answering.
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 Boleto payment in the Cash Out guide.
Mind one contract detail: POST .../boleto/pay answers 202 (asynchronous) with status: "PROCESSING", not 200 with an already-settled payment. The outcome arrives via boleto.paid / boleto.failed or via GET .../boleto/payments/{paymentId}.
4.4 PIX out — sync vs async (not “everything async”)
v2 keeps the same split as v1:
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.*, edi.*
During the migration window, 2 event families are not emitted:
MED: the
pix.med.opened/pix.med.updatedwebhooks are back, and so are the MED endpoints (see §4.2). Subscribing to those two events is now part of integrating MED: the dispute list is built from them.
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.edi.*— TBD; depends on the new batches pipeline.
(The pix.med.* webhooks have already been reactivated — see §4.2.)
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 fromCORPX_FEE_ACCOUNT_ID
How to reconcile via statement:
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)
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/dynamicPOST /v1/accounts/{id}/pix/qr-code/staticGET /v1/accounts/{id}/pix/qr-code/lookup(and the deprecated aliasGET /v1/accounts/{id}/pix/qr-code)DELETE /v1/accounts/{id}/pix/qr-code
v1: Finaya-style envelope:
v2: flat canonical shape, no envelope:
Field mapping:
HTTP status code: v2 returns 201 Created on creation (POST). GET stays 200.
How to migrate (minimum):
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
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: trueheader. Migrate to the canonical replacement before sunset. - removed: route existed only on v1; returns 404 on v2.
- added: new route; only exists on v2.
Summary (79 routes): 39 unchanged, 4 body-diff (PIX QR-code — flat JSON, §4.9; a fifth QR route also has a flattened response but is classified as deprecated), 13 changed (live statement, MED back with a new contract, boleto 503→200, etc.), 5 deprecated (aliases + sync QR, sunset 2026-11-21), 8 removed (404 on v2), 7 added, plus 2 switched off in 2026-05 (locked-balance) and 1 disabled (PATCH /pix/limits).
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.* / edi.* webhooks return?
Yes, in phases: fee.* on the next minor; edi.* TBD. The pix.med.* webhooks are already back, and so are the MED endpoints (§4.2).
Support
Contact: api@corpx.com — response within 1h on business hours.