How to migrate from the Payments API to the Orders API
The Orders API unifies online payment processing for , offering standardized endpoints, a transaction-level consolidated status model, and new native features that were not available in the Payments API. These include multiple transactions per order, manual or automatic processing, a dedicated capture endpoint, natively integrated 3DS 2.0 authentication, and a consolidated list of validation errors.
Migration involves updating request endpoints and fields, consolidating the status and notification model, and taking advantage of new native features. Migration does not involve changes to the business flow experienced by the buyer: the customer continues to complete the checkout within the seller's website, without redirects.
See below how to complete this migration, endpoint by endpoint and field by field, including the specific characteristics of each payment method.
Before implementing, classify each active Payments API flow into one of these situations:
- It has a direct equivalent: when it is a mandatory feature with a direct equivalent between the APIs, migrate your flow by following the required steps in this guide.
- It requires a technical adaptation: when it is an optional feature already part of your current integration, also implement the Based on your flow steps.
- It has no documented equivalent: keep the flow in the Payments API until the Orders API supports it.
The Payments API continues to work normally after the launch of the Orders API. Mercado Pago has not deactivated this API; it has simply stopped adding new features to it, while maintaining security and stability fixes. Technically, both APIs can remain active at the same time, each processing part of your volume.
We recommend the following strategy:
- Migrate by payment method, not all at once. Move cards to the Orders API first, then migrate the remaining payment methods one at a time after validating stability.
- Split traffic in your own checkout. Because each API uses independent endpoints, credentials, and idempotency keys, it is safe to route part of new transactions to /v1/ordersPOST and the rest to /v1/paymentsPOST.
- Handle notifications separately. Configure the Webhook to listen to the
ordersandpaymenttopics in parallel during the transition, routing each payload to the appropriate handler. Disablepaymentonly after migrating new traffic and monitoring outstanding Payments API transactions and notifications. - Define how to interrupt the migration. If you need to return to the Payments API, redirect only new transactions. Existing transactions must continue to be retrieved, captured, canceled, or refunded through the API in which they were processed, because they cannot and do not need to be transferred from one API to the other.
- Monitor the approval rate and integration quality separately for each API during coexistence, as a criterion for deciding when to stop sending traffic to the Payments API.
Before starting the migration, confirm that all the payment methods in your integration already have an equivalent in the Orders API.
In addition to payment method availability, check whether your integration uses Marketplace or Split Payments 1:1. In the Payments API, these integrations withhold commissions through application_fee; the Orders API does not yet have a documented equivalent field for this mechanism. Treat this as a blocker before migrating the flow.
In the Payments API, each operation used its own resource under /v1/payments/{id}. The Orders API consolidates most of these operations into subresources of the same /v1/orders/{order_id} and introduces dedicated endpoints that were not available before: explicit capture; adding, removing, and updating transactions; and manual-mode processing.
The Authorization header is required in both APIs and does not change. The main change is that the idempotency header becomes mandatory for almost all write operations.
| Header | Payments API | Orders API |
Authorization | Required in all requests | Required in all requests |
X-Idempotency-Key | Required only in /v1/paymentsPOST and /v1/payments/{id}/refundsPOST | Required in /v1/ordersPOST, /v1/orders/{order_id}/capturePOST, /v1/orders/{order_id}/transactionsPOST, /v1/orders/{order_id}/processPOST, /v1/orders/{order_id}/cancelPOST, and /v1/orders/{order_id}/refundPOST |
X-Idempotency-Key is reused with a different body, the Orders API returns the 409 error (idempotency_key_already_used). Always generate a new key for each transaction attempt, preferably a UUID v4. For more details about all headers accepted by the Orders API, see the API ReferenceAPI.In the Payments API, status and status_detail exist at a single level. In the Orders API, the same concept exists at two levels: the order status, as a consolidated view, and the status of each transaction in transactions.payments[]. This distinction becomes essential when an order has more than one transaction.
The following table maps the status values between a payment in the Payments API and the order and transaction levels in the Orders API.
| Payments API | Orders API (order) | Orders API (transaction) | Note |
pending | action_required | action_required | Awaiting action from the payer or seller. |
approved | processed | processed | Payment approved and credited. |
authorized | action_required (waiting_capture) | action_required (waiting_capture) | Amount reserved, awaiting capture. |
in_process | processing | processing | Under review or processing. |
in_mediation | charged_back (in_process) | charged_back (in_process) | Chargeback in progress. |
rejected | failed | failed | Rejected. status_detail provides the reason. |
cancelled | canceled | canceled | Canceled by the seller, buyer, or due to expiration. |
refunded | refunded | refunded | Fully refunded. |
charged_back | charged_back (settled / reimbursed) | charged_back (settled/reimbursed) | Chargeback received and resolved. |
cancelled, with two Ls, and the Orders API uses canceled, with one L. If your integration compares this status value as a literal string, update the spelling.The creation endpoint changes from /v1/paymentsPOST to /v1/ordersPOST. In addition to the URL, the request structure has been reorganized: the amount and payment method move into the transactions.payments[] node, an array that allows multiple transactions per order. The type field, with the fixed value online, and the config node are introduced without direct equivalents in the legacy API.
The following table maps the creation request structure field by field between the two APIs.
| Payments API | Orders API | Change |
transaction_amount | transactions.payments[].amount / total_amount | Moves into the transactions array and becomes a string. total_amount must match the sum of the transactions. |
payment_method_id | transactions.payments[].payment_method.id | Becomes nested under payment_method. |
token | transactions.payments[].payment_method.token | Becomes nested under payment_method. |
installments | transactions.payments[].payment_method.installments | Becomes nested under payment_method. |
statement_descriptor | transactions.payments[].payment_method.statement_descriptor | Becomes nested under payment_method. |
description | description | Unchanged. Remains at the root level. |
external_reference | external_reference | Name unchanged. Becomes required for some payment methods. |
notification_url | Not available. | Removed from the body. Notifications are now configured in Your integrations. Learn more in Notifications. |
capture | capture_mode | Changes from a boolean to the values manual, automatic, and automatic_async, at the order root level. |
date_of_expiration | transactions.payments[].expiration_time / date_of_expiration | Now accepts a duration in ISO 8601 format, in addition to an absolute date. |
payer.email | payer.email | Unchanged. |
payer.identification.type / .number | payer.identification.type / .number | Unchanged. |
payer.first_name / .last_name | payer.first_name / .last_name | Unchanged. |
payer.address.* | payer.address.* | Structure unchanged. |
three_d_secure_mode | config.online.transaction_security.validation and .liability_shift | Restructured. Learn more in Integrate 3DS. |
items[].id | items[].external_code | Renamed. |
items[].title / .unit_price / .quantity / .description / .picture_url / .category_id | Same names | Names unchanged. |
| Not available | type | New required field. For online payments, the value is online. |
| Not available | processing_mode | New field. Defines whether the order is processed in one step or later, through /process. |
| Not available | integration_data.{integrator_id, platform_id, sponsor.id} | New field. Partially replaces the legacy sponsor_id. |
issuer_id | transactions.payments[].payment_method.id (implicitly) | There is no documented direct issuer_id field. Validate the need on a case-by-case basis. |
binary_mode | Not documented as an Orders API field | Restricted the result to approved or rejected. No direct equivalent found. |
application_fee | Not documented in the Orders API. Validate with the team responsible for your integration before migrating integrations that use Split Payments 1:1. | |
fee_details[] | No documented direct equivalent | Fee details. Validate the calculation using money release reports. |
The following table maps the main fields in the creation response between the two APIs.
The Payments API returns one error at a time, the first one found. The Orders API returns a list with all request validation errors in a single response, making corrections faster.
The following table lists Payments API errors that were renamed or consolidated into more generic codes in the Orders API.
| HTTP | Payments API | Orders API | Note |
400 | 3000 to 3032 | property_value / property_type / required_properties | Consolidated into generic field validation codes. |
400 | 4000 to 4051 | required_properties / unsupported_properties / minimum_properties | Consolidated. |
400 | 23 | property_value | Invalid date_of_expiration format. |
400 | 2072 | invalid_total_amount | Renamed. Now validates the sum of transactions.payments[].amount against total_amount. |
400 | 2131 | invalid_order_type / property_value | Consolidated. |
400 | 4292 | empty_required_header | Renamed. |
401 | Unauthorized use of live credentials | invalid_credentials | Renamed. |
409 | 2001 | idempotency_key_already_used | Consolidated into the idempotency mechanism. |
403 | 4 (caller not authorized) | Not documented as a specific 403 error in the Orders API | Validate the behavior in the test environment. |
The retrieval endpoint changes from /v1/payments/{id}GET to /v1/orders/{id}GET. The response includes the complete order object, including all associated transactions, their refunds, and any chargebacks—information that required separate queries in the legacy API.
| Information | Payments API | Orders API |
| Refunds | Separate query at /v1/payments/{id}/refundsGET. | Included in transactions.refunds[] |
| Chargebacks | Separate query at /v1/chargebacks/{id}GET. | Referenced in transactions.chargebacks[], with id, transaction_id, case_id, status, and references. |
| 3DS data | payment_method.data.threeds | transactions.payments[].payment_method.transaction_security |
| Installments without Card and installment options | Not applicable to this endpoint. | config.payment_method.{default_type, installments_cost, installments.interest_free, installments.available} |
Retrieval errors
| HTTP | Error | Note |
400 | invalid_path_param | The submitted order_id has an invalid format. |
401 | invalid_credentials | Invalid or expired Access Token. |
404 | order_not_found | The order_id does not match any created order. |
500 | internal_error | Generic error. Try again and, if it persists, contact support with the x-request-id. |
The search endpoint changes from /v1/payments/searchGET to /v1/orders/searchGET, with restructured filters and pagination. Date ranges become required, and pagination uses page and page_size instead of offset and limit.
| Payments API | Orders API | Note |
sort | sort_by | Renamed. The default is created_date. |
criteria | sort_order | Renamed. The default is desc. |
begin_date / end_date | begin_date / end_date | Become required, in RFC3339 format. |
external_reference | external_reference | Unchanged. |
collector.id / payer.id | Not documented as filters. | Identity is obtained from the Access Token. |
offset / limit | page / page_size | Page-based pagination. page_size has a maximum of 100 and a default of 20. |
| Not available | status / status_detail / payment_method_id / payment_method_type | New direct filters. |
The amount reservation changes from a boolean field (capture) to a capture mode configured when creating the order (capture_mode), combined with a dedicated capture endpoint.
The following table compares how to reserve an amount without immediate capture in both APIs.
| Payments API | Orders API |
/v1/paymentsPOST with "capture": "false". | /v1/ordersPOST with "capture_mode": "manual". |
Result: "status": "authorized". | Result: "status": "action_required" and "status_detail": "waiting_capture". |
As in the Payments API, you can cancel an order before payment is completed or refund it, fully or partially, after approval. See below how each flow changes in the Orders API.
An order can only be canceled when its status is action_required or created, meaning the payment has not yet been completed. The endpoint changes from /v1/payments/{id}PUT to the dedicated /v1/orders/{order_id}/cancelPOST endpoint.
The Customers API endpoints are used by both the Payments API and the Orders API and do not change structure during migration. Only the way a saved card is used for a new charge changes, because the payment is now created as an order.
| Resource | Endpoint unchanged between APIs |
| Create customer | /v1/customersPOST |
| Search customers | /v1/customers/searchGET |
| Get customer | /v1/customers/{id}GET |
| Update customer | /v1/customers/{id}PUT |
| Save card | /v1/customers/{customer_id}/cardsPOST |
| List customer cards | /v1/customers/{customer_id}/cardsGET |
| Get card | /v1/customers/{customer_id}/cards/{id}GET |
| Update card | /v1/customers/{customer_id}/cards/{id}PUT |
| Delete card | /v1/customers/{customer_id}/cards/{id}DELETE |
| Customer addresses | /v1/customers/{id}/addressesPOST, /v1/customers/{id}/addressesGET, /v1/customers/{id}/addresses/{address_id}PUT, and /v1/customers/{id}/addresses/{address_id}DELETE |
What changes when paying with a saved card:
| Payments API | Orders API |
"payer.type": "customer"/ "payer.id": "<customer_id>" / token (generated using only the security code) | "payer.customer_id": "<customer_id>" / transactions.payments[].payment_method.token |
| /v1/paymentsPOST | /v1/ordersPOST |
In the Payments API, marketplace integrations use OAuth to obtain the connected seller's Access Token and send the application_fee field, with the amount withheld by the integrator, in the payment creation body.
application_fee. The integration_data.sponsor.id node exists, but it does not replace the commission withholding mechanism. If your integration depends on Split Payments 1:1 or a Marketplace model, validate this point before migrating this specific flow and do not assume parity.3D Secure 2.0 authentication changes from a simple field to a dedicated configuration node at config.online.transaction_security, with explicit control over chargeback liability.
| Payments API | Orders API | Description |
"three_d_secure_mode": "optional" | "config.online.transaction_security.validation": "on_fraud_risk" | Runs 3DS when the risk engine identifies that it is required. Recommended. |
"three_d_secure_mode": "not_supported" | config.online.transaction_security.validation: "never" | Explicitly disables 3DS. This is the default value. |
| Not available | config.online.transaction_security.liability_shift: "required" | Shifts chargeback liability to the issuer. Required when validation is anything other than never. |
Challenge response: "status": "pending", with creq and external_resource_url fields. | Challenge response: "status = action_required", "status_detail" = "pending_challenge", and URL at transactions.payments[].payment_method.transaction_security.url. | Renamed and restructured. |
| Challenge timeout not documented | 40-minute challenge timeout. | Timeframe explicitly defined. |
Restriction: "capture": "true" and "binary_mode": "false" required. | No equivalent restrictions documented. | Confirm the behavior with a capture_mode other than automatic in the test environment. |
Possible statuses after the 3DS flow in the Orders API:
status | status_detail | Description |
processed | accredited | Approved, with or without authentication. |
failed | failed | Rejected, without authentication or after authentication failure. |
action_required | pending_challenge | Authentication pending, for up to 40 minutes. |
canceled | expired | The challenge expired. A new order must be created. |
cardholder_name values used to simulate each scenario differ between the two APIs, so use the table specific to the Orders API. For more information about this flow, see Integrate 3DS.The HMAC-SHA256 signature and validation mechanism is identical in both APIs. What changes is the notification topic and where it is configured.
| Payments API | Orders API | Note |
payment topic | orders topic | Main change. Reconfigure the Webhook for the new topic. |
Configurable through notification_url in the body or in the panel. | Configurable only in the panel, under Your integrations | The option to configure it per request has been removed. |
| Resource to query: /v1/payments/{id}GET. | Resource to query: /v1/orders/{id}GET. | The endpoint changes. |
| IPN available and without signature validation. | Not available. | Use only Webhooks in the new integration. |
| 22-second response timeframe and retries every 15 minutes. | 22-second response timeframe and retries every 15 minutes. | Unchanged. |
The chargeback retrieval endpoint /v1/chargebacks/{id}GET is identical in both APIs. The difference is in the notification event and the new fields exposed directly in the order.
| Payments API | Orders API | Note |
Notification through the topic_chargebacks_wh topic | Notification through the Chargebacks event, in the chargebacks topic, with "action": "order.charged_back". | Configure this event in addition to Order (Mercado Pago). |
Payment status: charged_back | Order and transaction status: charged_back, with "status_detail": "in_process", "settled", or "reimbursed". | Additional status_detail information. |
| Retrieval via /v1/chargebacks/{id}GET. | Retrieval via /v1/chargebacks/{id}GET. | Unchanged. |
payment_id, /v1/chargebacks/{id}/documentationPOST to submit supporting documentation; and /v1/chargebacks/documentation/{type}/{uuid}GET to retrieve a previously submitted file.The resolution fields are identical in both APIs.
| Field | Value | Description |
coverage_applied | true | Decision in favor of the seller. The amount is returned to the seller. |
coverage_applied | false | Decision against the seller. The amount is deducted from the seller. |
Fraud prevention best practices remain conceptually the same. What changes is where additional data is sent in the request body.
| Practice | Payments API | Orders API |
| Device ID | Security script and X-meli-session-id header in /v1/paymentsPOST | /v1/ordersPOST (same mechanism) |
| Additional buyer and product data | additional_info.{items[], payer, shipments} | Distributed among items[] / payer / shipment (without the consolidated additional_info node) |
| Recognizable statement descriptor | statement_descriptor (at the root level) | transactions.payments[].payment_method.statement_descriptor |
| Industry data | additional_info.travel.{passengers, routes} | Data is sent in the order structure. See Industry data; the examples, including category_id, are not a closed list of values. |
The use of test credentials, users, and cards follows the same approach. The main difference is the email required for the payer and the table of cardholder names used to simulate each scenario.
cardholder_name values, see Test cards. For more details about the flow, see Test the integration.The Orders API evaluation considers the following aspects to measure the quality of the migrated integration.
| Evaluated aspect | Payments API | Orders API |
| Use of the official SDK for tokenization | Evaluated | Evaluated |
| Device ID | Evaluated | Evaluated |
Handling of status and status_detail | Evaluated | Evaluated (including the transaction level) |
Use of X-Idempotency-Key | Evaluated (only for creation and refunds) | Evaluated (for all write operations) |
| Reconciliation with multiple transactions | Not applicable | Evaluated |
| Use of 3DS when applicable | Not evaluated | Evaluated |
Before going to production, confirm the following:
- Activate production credentials in Your integrations.
- Replace the test Public Key and Access Token with their production versions.
- Implement an SSL/HTTPS certificate, which is required in production.
- Reconfigure the Webhook for the
orderstopic. Disable thepaymenttopic only after migrating new traffic and completing monitoring of outstanding Payments API transactions and notifications. - Ensure that all
statusandstatus_detailvalues are handled at both the order and transaction levels.
After applying the changes, verify that the integration works correctly in all flows before going to production. Use the checkboxes below to confirm each point.