# MD for: https://www.mercadopago.com.uy/developers/pt/docs/qr-code-ca/migrate-instore-orders-v2-to-orders.md \# How to migrate from the Instore Orders V2 API to the Orders API The Orders API unifies payment processing with QR Code on Mercado Pago, offering standardized endpoints, a consolidated status model, and new native features that did not exist in the Instore Orders V2 API. In addition, all new Mercado Pago features will be developed on the Orders API. Migrating from the Instore Orders V2 API to the Orders API involves \*\*updating endpoints and request fields\*\*, \*\*consolidating the notifications model\*\*, and taking advantage of \*\*new native features\*\*, such as dedicated cancel and refund endpoints. The main change in creation is that the Orders API returns \`201 Created\` with the complete order object, instead of \`204 No Content\` without a body. The migration does not involve changes to the business flow: the customer continues scanning the QR Code with the Mercado Pago app and confirming the payment. In addition, in this modality, the Orders API enables cash withdrawal (\*extracash\* and \*cash-out\*), payment method discounts, category discounts, and interest-free installments. For more details, see the \[transaction processing documentation\](https://www.mercadopago.com.uy/developers/en/docs/qr-code-ca/payment-processing). Read on to learn how to carry out this migration in full. ## Map endpoint changes Before starting the migration steps, refer to the table below for an overview of all endpoint changes. In the Instore Orders V2 API, each operation used a URL with the user, store, and POS identifier in the path. The Orders API consolidates these operations into standardized endpoints identified by the \`order\_id\`. | Operation | Instore Orders V2 API | Orders API | |---|---|---| | Create order | :TagComponent{tag="API" text="PUT /instore/qr/seller/collectors/{user\_id}/stores/{external\_store\_id}/pos/{external\_pos\_id}/orders" href="/developers/en/reference/instore\_orders/\_instore\_qr\_seller\_collectors\_user\_id\_stores\_external\_store\_id\_pos\_external\_pos\_id\_orders/put"} | :TagComponent{tag="API" text="POST /v1/orders" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/create-order/post"} | | Get order | :TagComponent{tag="API" text="GET /instore/qr/seller/collectors/{user\_id}/pos/{external\_pos\_id}/orders" href="/developers/en/reference/instore\_orders/\_instore\_qr\_seller\_collectors\_user\_id\_pos\_external\_pos\_id\_orders/get"} | :TagComponent{tag="API" text="GET /v1/orders/{order\_id}" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/get-order/get"} | | Cancel order | :TagComponent{tag="API" text="DELETE /mpmobile/instore/qr/{user\_id}/{external\_id}" href="/developers/en/reference/instore\_orders/\_mpmobile\_instore\_qr\_user\_id\_external\_id/delete"} | :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/cancel" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/cancel-order/post"} | | Refund order | Via Payments API (no dedicated endpoint in the Instore Orders V2 API) | :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/refund" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/refund-order/post"} | ## Update the status and notifications model One of the most significant changes between the Instore Orders V2 API and the Orders API is the \`status\` model. In the Instore Orders V2 API, the state of a transaction was determined by monitoring notifications from two independent topics , \`payments\` and \`merchant\_orders\`, with distinct fields and values for each. The Orders API unifies this behavior in a single \`status\` field on the order. See the complete mapping below. In addition, the Orders API also introduces the \`status\_detail\` field, which provides greater detail on the transaction state. The possible values are \`created\`, \`canceled\`, \`accredited\`, \`refunded\`, and \`expired\`. :::AccordionComponent{title="Map status values" pill="1"} In the Instore Orders V2 API, the \`merchant\_order\` is created when the order is generated, so notifications from the \`merchant\_orders\` topic are available from the beginning of the flow. The \`payment\` object, on the other hand, is only created when there is a payment attempt, whether approved or rejected. Anyone monitoring only the \`payments\` topic received no notifications until the user started the payment process. In the Orders API, this behavior is unified: the \`status\` field of the order reflects the complete state from creation, without the need to monitor two independent topics. | \`payments\` topic (\`status\`) | \`merchant\_orders\` topic (\`status\` / \`order\_status\`) | Orders API (\`status\`) | Note | |---|---|---|---| | — | \`opened\` (no payment or with rejected payment) | \`created\` | In the Instore Orders V2 API, \`opened\` indicated that the order did not yet have an approved payment. In the Orders API, this state is explicit from creation. | | \`approved\` | \`closed\` + \`order\_status: "paid"\` | \`processed\` | Both topics converged on the same result: approved payment. In the Orders API, the state is self-contained. | | \`rejected\` | \`opened\` (the \`merchant\_order\` does not change state and remains open) | No equivalent | In the Instore Orders V2 API, a rejected payment left the \`merchant\_order\` in \`opened\` awaiting a new attempt. In the Orders API, rejected payments are not exposed. The state remains \`created\` until an approved payment is received. | | \`refunded\` | \`closed\` + \`order\_status: "reverted"\` | \`refunded\` | Full refund. In the \`merchant\_orders\` topic, identifiable by \`order\_status: "reverted"\`. | | — | — | \`canceled\` | New status with no equivalent in the Instore Orders V2 API. | | — | — | \`expired\` | New status with no equivalent in the Instore Orders V2 API. | ::: Regarding notifications, in the Legacy API they could be configured via the \`notification\_url\` field in the request body (IPN model) or via webhooks with the \`payments\` and \`merchant\_orders\` topics. In the Orders API, the \`notification\_url\` field does not exist and the \`payments\` and \`merchant\_orders\` topics are not compatible. Configure notifications in \[Your integrations\](https://www.mercadopago.com.uy/developers/panel/app), subscribing to the \*\*"Order (Mercado Pago)"\*\* (\`orders\`) topic, before going to production. For more information, see the \[notifications documentation\](https://www.mercadopago.com.uy/developers/en/docs/qr-code-ca/notifications). ## Adapt headers The Orders API introduces a mandatory header change that affects all endpoints. The Access Token must be sent via the \`Authorization\` header. Sending it as a query parameter is not allowed in the Orders API. Apply the change below before testing any other resource. :::AccordionComponent{title="Add the X-Idempotency-Key header" pill="1"} The \`X-Idempotency-Key\` header is required for order creation, cancellation, and refund operations. It ensures that a repeated request with the same key returns the original result without processing the operation again. Send a UUID v4 or unique random string per request. \*\*GET\*\* operations do not require this header. > WARNING > > If the same \`X-Idempotency-Key\` is reused with a different body, the API will return the \`idempotency\_key\_already\_used\` error. Generate a new key for each distinct operation. ::: ## Migrate order creation The creation endpoint changes from :TagComponent{tag="API" text="PUT /instore/qr/seller/collectors/{user\_id}/stores/{external\_store\_id}/pos/{external\_pos\_id}/orders" href="/developers/en/reference/instore\_orders/\_instore\_qr\_seller\_collectors\_user\_id\_stores\_external\_store\_id\_pos\_external\_pos\_id\_orders/put"} to :TagComponent{tag="API" text="POST /v1/orders" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/create-order/post"}. In addition to the method change from PUT to POST and the URL change, the response changes from HTTP \`204 No Content\` without a body to \`201 Created\` with the complete order object. The \`{user\_id}\` and the store and POS path parameters are removed. Identity comes from the Access Token and the POS is identified by the \`config.qr.external\_pos\_id\` field in the body. Follow the steps below to adapt the request, the response, and error handling. > NOTE > > The Instore Orders V2 API returned HTTP \`204 No Content\` without a body, making it impossible to obtain the order \`id\` for subsequent operations. The Orders API returns \`201 Created\` with the complete order object. The \`id\` field from the \*response\* is required for subsequent queries, cancellations, and refunds. :::::AccordionComponent{title="Map request body fields" pill="1"} In the Orders API, the user identity is obtained directly from the Access Token. The \`{external\_store\_id}\` and \`{user\_id}\` are no longer path parameters. The POS is now identified by the \`config.qr.external\_pos\_id\` field in the body. The \`type\` field (with value \`"qr"\`) and \`external\_reference\` become required. The table below shows the fields that existed in both APIs and changed during migration. | Instore Orders V2 API | Orders API | Description | Change | |---|---|---|---| | \`{user\_id}\` (path param) | Does not exist | Mercado Pago user identifier. In the Orders API, identity is obtained directly from the Access Token. | No longer a path parameter. | | \`{external\_store\_id}\` (path param) | Does not exist | External store identifier. | No longer a path parameter. The store is identified indirectly through the POS. | | \`{external\_pos\_id}\` (path param) | \`config.qr.external\_pos\_id\` | External POS identifier. In the Instore Orders V2 API, it was sent in the path. In the Orders API, it is sent in the body inside \`config.qr\`. | No longer a path parameter; moves to the body. | | \`external\_reference\` | \`external\_reference\` | Reference that can sync with the integrator's sales system. In the Orders API, it is required, with a maximum of 64 characters, only letters, numbers, \`-\` and \`\_\`. Cannot contain PII data. | Becomes \*\*required\*\*. | | \`title\` | Does not exist | Purchase title. In the Orders API, description is sufficient. | Removed. | | \`description\` | \`description\` | Purchase description. In the Instore Orders V2 API, it existed at both the root level and inside \`items\[\]\`. In the Orders API, it only exists at the root level. Max. 150 characters. Must not contain PII data. | Remains at root level. The version inside \`items\[\]\` was removed. | | \`items\[\].description\` | Does not exist | Item description. | Removed. Description is managed only at the order root level. | | \`notification\_url\` | Does not exist | URL to receive payment or merchant\_order notifications. In the Orders API, notifications are configured as a webhook in your application within Your integrations by selecting the \*\*"Order (Mercado Pago)"\*\* topic. For more information, see the \[notifications documentation\](https://www.mercadopago.com.uy/developers/en/docs/qr-code/notifications). | Removed from the body. | | \`expiration\_date\` | \`expiration\_time\` | Order validity. In the Instore Orders V2 API, it was an absolute date (e.g., \`2020-08-22T16:34:56.559-04:00\`). In the Orders API, it is a relative duration in ISO 8601 format (e.g., \`PT5M\`). The minimum value is \`PT30S\` and the maximum value is \`PT3H\`. For \`static\` mode, the default value is \`PT10M\`. Values greater than 10 minutes are ignored. For \`dynamic\` mode, the default is \`PT15M\` and the value sent is respected. For \`hybrid\` mode, the dynamic QR Code defaults to \`PT15M\` (respected); the static QR Code has a maximum of \`PT10M\` (higher values are ignored). | Changes from absolute date to relative duration in ISO 8601 format. | | \`sponsor.id\` | \`integration\_data.sponsor.id\` | \`USER\_ID\` of the Mercado Pago account of the integrating system. | Moves to the \`integration\_data.sponsor\` node. | | \`items\[\].sku\_number\` | \`items\[\].external\_code\` | Item code in the external system. | Renamed to \`items\[\].external\_code\`. | | \`items\[\].category\` | Does not exist | Item category. | Removed. Has no equivalent in the Orders API. | | \`items\[\].title\` | \`items\[\].title\` | Item name. In the Orders API, max. 150 characters. | Becomes \*\*required\*\*. | | \`items\[\].unit\_price\` | \`items\[\].unit\_price\` | Item unit price. In the Instore Orders V2 API, it was a number. In the Orders API, it is a decimal \`string\`. Accepts two decimal places (e.g., \`"15.00"\`) or none. | Changes from number to decimal \`string\` and becomes \*\*required\*\*. | | \`items\[\].quantity\` | \`items\[\].quantity\` | Item quantity. In the Instore Orders V2 API, it was a number. In the Orders API, it is an \`integer\`. | Changes from number to \`integer\` and becomes \*\*required\*\*. | | \`items\[\].unit\_measure\` | \`items\[\].unit\_measure\` | Item unit of measure. Max. 10 characters. | Becomes \*\*required\*\*. | | \`items\[\].total\_amount\` | Does not exist | Total items amount (unit price × quantity). | Removed. Has no equivalent in the Orders API. | | \`items\[\].external\_categories\[\].id\` | \`items\[\].external\_categories\[\].id\` | Item category identifier in the external system. Enables category-based discounts. Cannot be used together with \`discounts\`. | No change. | | \`marketplace\_fee\` | \`marketplace\_fee\` | Marketplace fee amount. In the Instore Orders V2 API, it was a number. In the Orders API, it is a decimal \`string\`. Accepts two decimal places (e.g., \`"15.00"\`) or none. Exclusive to OAuth integrations. | Changes from number to decimal \`string\`. | | \`cash\_out\[\].amount\` | \`transactions.cash\_outs\[\].amount\` | Cash withdrawal amount. In the Instore Orders V2 API, it was the \`cash\_out\[\].amount\` field directly. In the Orders API, it moves to the \`transactions.cash\_outs\` node. Accepts two decimal places (e.g.: \`"15.00"\`) or none. Only 1 cash withdrawal transaction per order. | Moves to the \`transactions.cash\_outs\` node. | | \`total\_amount\` | \`transactions.payments\[\].amount\` | Total transaction amount. In the Instore Orders V2 API, it was the \`total\_amount\` field at root level. Only 1 payment transaction per order is allowed when \`type\` is \`qr\`. In the Orders API, the payment amount is sent inside \`transactions.payments\[\]\`. Accepts two decimal places (e.g., \`"15.00"\`) or none. | Moves to the \`transactions.payments\` node. Changes from number to decimal \`string\`. | | \`X-platform-id\` (\*header\*) | \`integration\_data.platform\_id\` | Platform identifier, assigned by Mercado Pago. | Moves from \*header\* to \*body\*. | | \`X-integrator-id\` (\*header\*) | \`integration\_data.integrator\_id\` | Integrator identifier, assigned by Mercado Pago. Must have the \`dev\_\` prefix. | Moves from \*header\* to \*body\*. | | \`discounts\[\].net\_amount\` (\*decimal\*) | \`discounts.payment\_methods\[\].new\_total\_amount\` (\*string\*) | Final amount to be charged to the customer for the configured payment method. Accepts two decimal places (e.g., \`"15.00"\`) or none. Cannot be used together with \`items\[\].external\_categories\`. | Changes type and structure. The \`discounts\[\]\` node is restructured into \`discounts.payment\_methods\[\]\`. | | \`discounts\[\].criteria.payment\_method.payment\_type\_id\` | \`discounts.payment\_methods\[\].type\` | Payment method type to which the discount applies. The possible values are \`debit\_card\`, \`credit\_card\`, \`account\_money\`, \`prepaid\_card\`. | Structure simplified: \`criteria.payment\_method.payment\_type\_id\` becomes \`type\`. | The Orders API also introduces the following fields with no equivalent in the Instore Orders V2 API. | Field | Description | |---|---| | \`type\` | Order type. For QR Code payments, the only possible value is \`"qr"\`. \*\*Required.\*\* | | \`total\_amount\` | Total order amount. Represents the sum of all transactions. If there is a cash withdrawal, it must be the sum of the payment amount and the withdrawal amount. Accepts two decimal places (e.g., \`"15.00"\`) or none. | | \`config.qr.mode\` | QR Code mode. The possible values are \`static\` (static QR Code associated with the POS, equivalent to the Instore Orders V2 API behavior), \`dynamic\` (unique QR Code per transaction, returned in \`type\_response.qr\_data\`), \`hybrid\` (both modes in parallel). The default value is \`static\`. | | \`items\[\].external\_code\` | Code that identifies the item in the external system (e.g., EAN). Max. 30 characters. | > SUCCESS\_MESSAGE > > See all available parameters in the :TagComponent{tag="API" text="API Reference" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/create-order/post"}. ::::: :::AccordionComponent{title="Adapt creation response fields" pill="2"} The Instore Orders V2 API returned HTTP \`204 No Content\` without a body on creation. The Orders API returns \`201 Created\` with the complete order object. The table below shows the fields of the new API's response. | Field | Description | |---|---| | \`id\` | Identifier of the created order (e.g., \`ORD00001111222233334444555566\`). In the Instore Orders V2 API, there was no body in the creation response. Required for subsequent queries, cancellations, and refunds. | | \`user\_id\` | Identifier of the Mercado Pago user who created the order. Renamed from \`collector\_id\` (integer) to \`user\_id\` (string). | | \`total\_amount\` | Total order amount. Changes from number to decimal \`string\`. | | \`external\_reference\` | External reference of the order. | | \`description\` | Product or service description. Remains at root level. The version inside \`items\[\]\` was removed. | | \`marketplace\_fee\` | Marketplace fee. Changes from number to decimal \`string\`. | | \`integration\_data.sponsor.id\` | \`USER\_ID\` of the integrating system. Moved from \`sponsor\_id\` (integer) to \`integration\_data.sponsor\` (string). | | \`country\_code\` | Site/country identifier (e.g., \`"AR"\`). Renamed from \`site\_id\`. Format changes from lowercase to uppercase. | | \`type\` | Order type. For QR Code: always \`qr\`. | | \`expiration\_time\` | Expiration duration in ISO 8601 format. Replaces \`expiration\_date\` (absolute date) with a relative duration. | | \`processing\_mode\` | Processing mode. For QR Code: always \`automatic\`. Replaces the \`processing\_modes\` array. | | \`status\` | Current order status. When created it is \`created\`. In the Instore Orders V2 API, status was only available via webhooks. | | \`status\_detail\` | Current order status detail. The possible values are \`created\`, \`canceled\`, \`accredited\`, \`refunded\`, \`expired\`. | | \`currency\` | Currency identifier. The possible values are \`ARS\`, \`BRL\`, \`CLP\`, \`UYU\`. In the Instore Orders V2 API, currency came from \`items\[\].currency\_id\`. | | \`created\_date\` | Creation date. Format \`yyyy-MM-ddTHH:mm:ss.sssZ\`. | | \`last\_updated\_date\` | Last updated date. Format \`yyyy-MM-ddTHH:mm:ss.sssZ\`. | | \`config.qr.external\_pos\_id\` | External identifier of the POS associated with the order. In the Instore Orders V2 API, it was an input path parameter and was not returned. | | \`config.qr.mode\` | Configured QR Code mode. The possible values are \`static\`, \`dynamic\`, \`hybrid\`. For integrations migrated from the Instore Orders V2 API, the value is \`static\`. | | \`transactions.payments\[\].id\` | Payment transaction identifier. In the Instore Orders V2 API, the payment ID only arrived via webhook from the \`payments\` topic. | | \`transactions.payments\[\].amount\` | Payment amount. In the Instore Orders V2 API, the amount was in \`total\_amount\` at the order level. | | \`transactions.payments\[\].status\` | Payment status. When created it is \`created\`. | | \`transactions.payments\[\].status\_detail\` | Payment status detail. When created it is \`ready\_to\_process\`. | | \`transactions.cash\_outs\[\].id\` | Cash withdrawal transaction identifier, generated by Mercado Pago. | | \`transactions.cash\_outs\[\].amount\` | Cash withdrawal amount. | | \`transactions.cash\_outs\[\].status\` | Cash withdrawal status. When created it is \`created\`. The possible values are \`created\`, \`canceled\`, \`processed\`, \`expired\`. | | \`transactions.cash\_outs\[\].status\_detail\` | Cash withdrawal status detail. When created it is \`ready\_to\_process\`. | | \`type\_response.qr\_data\` | QR Code string for conversion into a QR Code. Present only when \`config.qr.mode\` is \`dynamic\` or \`hybrid\`. | | \`integration\_data.application\_id\` | Mercado Pago application identifier. | | \`integration\_data.platform\_id\` | Platform identifier, assigned by Mercado Pago. | | \`integration\_data.integrator\_id\` | Integrator identifier, assigned by Mercado Pago. | | \`items\[\].title\` | Item name. | | \`items\[\].unit\_price\` | Item unit price. | | \`items\[\].quantity\` | Item quantity. | | \`items\[\].unit\_measure\` | Item unit of measure. | | \`items\[\].external\_code\` | External item code (e.g., EAN). | | \`items\[\].external\_categories\[\].id\` | Item category identifier in the external system. | | \`discounts.payment\_methods\[\].new\_total\_amount\` | New total order amount when a discount is applied. | | \`discounts.payment\_methods\[\].type\` | Payment method to which the discount applies. | ::: :::AccordionComponent{title="Update error handling in creation" pill="3"} The Orders API consolidates most field-specific validation errors into the generic codes \`property\_value\` and \`property\_type\`, with the field detail in the response body. In addition, response messages are more descriptive, making it easier to identify and resolve issues. See the tables below for more details. ### Errors that change behavior The following errors exist in both APIs but behave differently in the Orders API. | HTTP | Instore Orders V2 API | Orders API | Note | |---|---|---|---| | \`400\` | Multiple: \`invalid\_collectorId\`, \`invalid\_externalStoreId\`, \`invalid\_externalPosId\`, \`invalid\_total\_amount\`, \`invalid\_items.\*\`, \`invalid\_cash\_out.\*\` and others | \`property\_value\` | \`property\_value\` consolidates field-level validation errors. The field with the error is indicated in the response detail. | | \`400\` | \`error\_creating\_seller\_qr\_order\` (malformed JSON) | \`property\_type\` | The Orders API distinguishes malformed JSON with its own code instead of the generic \`error\_creating\_seller\_qr\_order\`. | | \`400\` | \`error\_creating\_seller\_qr\_order\` (general errors) | \`bad\_request\` | The generic \`error\_creating\_seller\_qr\_order\` code covered multiple errors. The Orders API uses \`bad\_request\` for cases not covered by other codes. | | \`400\` | \`error\_invalid\_sponsor\_id\` | \`sponsor\_id\_not\_valid\` | The field migrates to \`integration\_data.sponsor.id\`. | | \`400\` → \`401\` | authentication via middleware | \`unauthorized\` | In the Instore Orders V2 API, failed authentication returned \`400\`. In the Orders API, it is an explicit \`401\`. | | \`400\` | \`in\_store\_order\_creation\_error\` (seller not enabled for \`cash\_out\`) | \`seller\_configuration\` | The seller is not authorized for \`cash\_out\`. The new API exposes its own code for this case. | | \`404\` | \`pos\_obtainment\_error\` | \`pos\_not\_found\` | The \`external\_pos\_id\` is no longer a path parameter and moves to the body. | ### Errors that remain unchanged The following error has the same behavior in both APIs. | HTTP | Error | Note | |---|---|---| | \`500\` | Generic | Internal error. Verify the response and retry the request. | ### Errors introduced by the Orders API The following errors have no equivalent in the Instore Orders V2 API. | HTTP | Error | Note | |---|---|---| | \`400\` | \`marketplace\_not\_valid\` | The Access Token was not obtained via OAuth and it is not possible to identify a valid marketplace. | | \`400\` | \`empty\_required\_header\` | \`X-Idempotency-Key\` is missing. | | \`400\` | \`unsupported\_site\` | Attempt to create an order in an unsupported country. | | \`400\` | \`unsupported\_properties\` | Unsupported property in the body. The field with the error is indicated in the response detail. | | \`404\` | \`marketplace\_fee\_not\_allowed\` | Sending \`marketplace\_fee\` is not allowed because the marketplace was not found. | | \`409\` | \`idempotency\_key\_already\_used\` | The \`X-Idempotency-Key\` has already been used with a different request in the last 24 hours. | ::: ## Update order queries The query endpoint changes from :TagComponent{tag="API" text="GET /instore/qr/seller/collectors/{user\_id}/pos/{external\_pos\_id}/orders" href="/developers/en/reference/instore\_orders/\_instore\_qr\_seller\_collectors\_user\_id\_pos\_external\_pos\_id\_orders/get"} to :TagComponent{tag="API" text="GET /v1/orders/{order\_id}" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/get-order/get"}. In the Instore Orders V2 API, queries were made by POS (\`external\_pos\_id\`), returning the active order at that POS \*\*without exposing the transaction state\*\*. The Orders API replaces the POS-based query endpoint with one that operates directly by \`order\_id\` and returns the complete transaction state: \`status\`, payments, refunds, and payment method data. In the Instore Orders V2 API, this information only arrived via notification. \`\`\`curl curl -X GET \\ 'https://api.mercadopago.com/v1/orders/{{ORDER\_ID}}' \\ -H 'Authorization: Bearer {{ACCESS\_TOKEN}}' \`\`\` :::AccordionComponent{title="Adapt query response fields" pill="1"} The table below shows the query response fields that existed in both APIs and changed during migration. | Instore Orders V2 API | Orders API | Description | Change | |---|---|---|---| | Does not exist | \`id\` | Order identifier. In the Instore Orders V2 API, the query endpoint did not return the order ID. | New field. | | Does not exist | \`user\_id\` | Identifier of the user who created the order. In the Instore Orders V2 API, it was a path parameter but was not returned in the response. | New field. | | Does not exist | \`type\` | Order type. For QR Code: always \`qr\`. | New field. | | \`external\_reference\` | \`external\_reference\` | External reference of the order. | No change. | | \`description\` | \`description\` | Purchase description. | No change. | | \`title\` | Does not exist | Purchase title. | Removed. | | \`notification\_url\` | Does not exist | URL configured for notifications. | Removed. Configure webhook notifications in your application. For more information, see the \[notifications documentation\](https://www.mercadopago.com.uy/developers/en/docs/qr-code/notifications). | | \`expiration\_date\` | \`expiration\_time\` | Order validity. In the Instore Orders V2 API, it was an absolute date. In the Orders API, it is a relative duration in ISO 8601 format. | Changes from absolute date to relative duration. | | Does not exist | \`processing\_mode\` | Processing mode. For QR Code: always \`automatic\`. | New field. | | \`total\_amount\` | \`total\_amount\` | Total order amount. In the Instore Orders V2 API, it was a number. In the Orders API, it is a decimal \`string\`. | Changes from number to decimal \`string\`. | | Does not exist | \`country\_code\` | Site/country identifier. | New field. | | \`marketplace\_fee\` | \`marketplace\_fee\` | Marketplace fee. In the Instore Orders V2 API, it was a number. In the Orders API, it is a decimal \`string\`. | Changes from number to decimal \`string\`. | | \`sponsor.id\` | \`integration\_data.sponsor.id\` | \`USER\_ID\` of the integrating system. In the Instore Orders V2 API, it was an \`integer\`. In the Orders API, it is a \`string\`. | Moves to \`integration\_data.sponsor\`. Changes from \`integer\` to \`string\`. | | \`items\[\].title\` | \`items\[\].title\` | Item name. | No change. | | \`items\[\].unit\_price\` | \`items\[\].unit\_price\` | Item unit price. In the Instore Orders V2 API, it was a number. In the Orders API, it is a decimal \`string\`. | Changes from number to decimal \`string\`. | | \`items\[\].quantity\` | \`items\[\].quantity\` | Item quantity. | No change. | | \`items\[\].unit\_measure\` | \`items\[\].unit\_measure\` | Item unit of measure. | No change. | | \`items\[\].external\_categories\[\].id\` | \`items\[\].external\_categories\[\].id\` | Item category identifier in the external system. | No change. | When migrating to the Orders API, the following \`items\[\]\` fields were \*\*removed\*\* and have no direct equivalent. | Field | Note | |---|---| | \`items\[\].description\` | Removed. Description is managed only at the order root level. | | \`items\[\].sku\_number\` | \`items\[\].external\_code\` | Item code in the external system. | Renamed to \`items\[\].external\_code\`. | | \`items\[\].category\` | Removed. Has no equivalent in the Orders API. | | \`items\[\].currency\_id\` | Removed. Currency is exposed at the order level in the \`currency\` field. | | \`items\[\].total\_amount\` | Removed. Has no equivalent in the Orders API. | | \`items\[\].discount\` | Removed. The discount structure moved to \`discounts.payment\_methods\[\]\`. | The Orders API also introduces the following fields in the query response. | Field | Description | |---|---| | \`status\` | Current order status. The possible values are \`created\`, \`canceled\`, \`processed\`, \`refunded\`, \`expired\`. In the Instore Orders V2 API, status was only visible via \`payments\` and \`merchant\_orders\` notifications. | | \`status\_detail\` | Current order status detail. The possible values are \`created\`, \`canceled\`, \`accredited\`, \`refunded\`, \`expired\`. | | \`currency\` | Currency identifier. The possible values are \`ARS\`, \`BRL\`, \`CLP\`, \`UYU\`. | | \`created\_date\` | Creation date. Format \`yyyy-MM-ddTHH:mm:ss.sssZ\`. | | \`last\_updated\_date\` | Last updated date. Format \`yyyy-MM-ddTHH:mm:ss.sssZ\`. | | \`config.qr.external\_pos\_id\` | External identifier of the POS associated with the order. | | \`config.qr.mode\` | Configured QR Code mode. | | \`integration\_data.application\_id\` | Mercado Pago application identifier. | | \`integration\_data.platform\_id\` | Platform identifier, assigned by Mercado Pago. | | \`integration\_data.integrator\_id\` | Integrator identifier, assigned by Mercado Pago. | | \`transactions.payments\[\].id\` | Payment transaction identifier. In the Instore Orders V2 API, the payment ID only arrived via webhook from the \`payments\` topic. | | \`transactions.payments\[\].amount\` | Payment amount. | | \`transactions.payments\[\].refunded\_amount\` | Refunded amount. Present only when a refund occurred. | | \`transactions.payments\[\].paid\_amount\` | Total paid amount. | | \`transactions.payments\[\].status\` | Current payment status. | | \`transactions.payments\[\].status\_detail\` | Detailed payment status. | | \`transactions.payments\[\].reference\_id\` | Identifier of the payment associated with the order. | | \`transactions.payments\[\].payment\_method.type\` | Selected payment method. | | \`transactions.payments\[\].payment\_method.installments\` | Number of installments. | | \`transactions.payments\[\].payment\_method.id\` | Payment method or card brand identifier. | | \`transactions.payments\[\].discounts.type\` | Payment method with which the discount was applied. Present only if a discount was applied. | | \`transactions.cash\_outs\[\].id\` | Cash withdrawal transaction identifier, generated by Mercado Pago. | | \`transactions.cash\_outs\[\].amount\` | Cash withdrawal amount. | | \`transactions.cash\_outs\[\].status\` | Cash withdrawal status. When created it is \`created\`. The possible values are \`created\`, \`canceled\`, \`processed\`, \`expired\`. | | \`transactions.cash\_outs\[\].status\_detail\` | Cash withdrawal status detail. When created it is \`ready\_to\_process\`. | | \`transactions.refunds\[\].id\` | Refund identifier. | | \`transactions.refunds\[\].transaction\_id\` | Identifier of the refunded payment transaction. | | \`transactions.refunds\[\].reference\_id\` | Identifier that associates the transaction with the refund. | | \`transactions.refunds\[\].amount\` | Refund amount. | | \`transactions.refunds\[\].status\` | Refund status. | | \`items\[\].external\_code\` | External item code (e.g., EAN). | ::: :::AccordionComponent{title="Update error handling in queries" pill="2"} In queries, errors were grouped by type of change. See the tables below for more details. ### Errors that disappear The following errors exist in the Instore Orders V2 API but \*\*have no equivalent\*\* in the Orders API. | HTTP | Instore Orders V2 API | Note | |---|---|---| | \`400\` | \`invalid\_collectorId\` | Removed in the Orders API. Queries operate on the \`order\_id\`. | | \`400\` | \`invalid\_externalPosId\` | Removed. Queries operate on \`order\_id\`, not on the POS. | | \`400\` | \`pos\_obtainment\_by\_external\_id\_error\` | Removed. The concept of POS as a query parameter disappears. | | \`400\` | \`pos\_deleted\_error\` | Removed in the Orders API. | | \`403\` | \`forbidden\` | Removed in the Orders API. | ### Renamed errors The following errors were renamed in the Orders API. | HTTP | Instore Orders V2 API | Orders API | Note | |---|---|---|---| | \`404\` | \`point\_of\_sale\_in\_store\_order\_not\_found\` and \`in\_store\_order\_not\_found\` | \`order\_not\_found\` | Both not found cases are unified into \`order\_not\_found\`. | ### Errors that change behavior The following errors exist in both APIs but with different behavior in the Orders API. | HTTP | Instore Orders V2 API | Orders API | Note | |---|---|---|---| | \`400\` → \`401\` | authentication via middleware | \`unauthorized\` | In the Instore Orders V2 API, it was \`400\`. In the Orders API, it is an explicit \`401\`. | ### Errors introduced by the Orders API The following errors have no equivalent in the Instore Orders V2 API. | HTTP | Error | Note | |---|---|---| | \`400\` | \`invalid\_path\_param\` | The \`order\_id\` has an invalid format. Must start with \`ORD\` followed by 26 characters. | | \`401\` | \`unauthorized\` | Invalid or expired Access Token. | | \`404\` | \`order\_not\_found\` | The \`order\_id\` does not correspond to any created order. | | \`500\` | Generic | Internal error. Verify the response and retry the request. | ::: ## Update order cancellation The cancellation endpoint changes from :TagComponent{tag="API" text="DELETE /mpmobile/instore/qr/{user\_id}/{external\_id}" href="/developers/en/reference/instore\_orders/\_mpmobile\_instore\_qr\_user\_id\_external\_id/delete"} to :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/cancel" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/cancel-order/post"}. The HTTP method changes from \`DELETE\` to \`POST\`, the \`{user\_id}\` and \`{external\_id}\` are removed from the path, and cancellation now operates on the \`order\_id\`. The response changed from empty (HTTP \`204\`) to the complete order object with \`status: "canceled"\` (HTTP \`200\`). In the Instore Orders V2 API, cancellation deleted the active order associated with the POS, an operation on the POS rather than the individual transaction. In the Orders API, cancellation operates directly on the order via \`order\_id\`, without depending on the POS in the path. :::AccordionComponent{title="Adapt cancellation headers" pill="1"} The response returns the complete order object with \`status: "canceled"\`. The header required for this operation is: | Header | Required | Description | |---|---|---| | \`X-Idempotency-Key\` | Required | Unique key per request. | The response returns the complete order object with \`status: "canceled"\`. In the Instore Orders V2 API, cancellation returned HTTP \`204\` without a body. The table below lists the fields returned in the cancellation response. | Field | Description | |---|---| | \`id\` | Identifier of the cancelled order, generated by Mercado Pago. | | \`user\_id\` | Identifier of the Mercado Pago user who created the order. | | \`type\` | Order type. For QR Code: always \`qr\`. | | \`external\_reference\` | External reference of the order, assigned at creation. | | \`description\` | Product or service description, the reason for the order. | | \`expiration\_time\` | Order expiration time in ISO 8601 format. | | \`processing\_mode\` | Processing mode. For QR Code: always \`automatic\`. | | \`total\_amount\` | Total order amount. | | \`country\_code\` | Site/country identifier of the Mercado Pago application. | | \`marketplace\_fee\` | Marketplace fee. Exclusive to OAuth integrations. | | \`integration\_data.application\_id\` | Identifier of the Mercado Pago application that created the order. | | \`integration\_data.platform\_id\` | Platform identifier, assigned by Mercado Pago. | | \`integration\_data.integrator\_id\` | Integrator identifier, assigned by Mercado Pago. | | \`integration\_data.sponsor.id\` | \`USER\_ID\` of the integrating system. | | \`status\` | Current order status. When canceled it is \`canceled\`. | | \`status\_detail\` | Current order status detail. When canceled it is \`canceled\`. | | \`currency\` | Currency identifier used. The possible values are \`ARS\`, \`BRL\`, \`CLP\`, \`UYU\`. | | \`created\_date\` | Order creation date. Format \`yyyy-MM-ddTHH:mm:ss.sssZ\`. | | \`last\_updated\_date\` | Order last updated date. Format \`yyyy-MM-ddTHH:mm:ss.sssZ\`. | | \`config.qr.external\_pos\_id\` | External identifier of the POS associated with the order. | | \`config.qr.mode\` | Configured QR Code mode. The possible values are \`static\`, \`dynamic\`, \`hybrid\`. For integrations migrated from the Instore Orders V2 API, the value is \`static\`. | | \`transactions.payments\[\].id\` | Payment transaction identifier, generated by Mercado Pago. | | \`transactions.payments\[\].amount\` | Payment amount. | | \`transactions.payments\[\].status\` | Payment status. When canceled it is \`canceled\`. | | \`transactions.payments\[\].status\_detail\` | Payment status detail. When canceled it is \`canceled\_by\_api\`. | | \`transactions.cash\_outs\[\].id\` | Cash withdrawal transaction identifier, generated by Mercado Pago. | | \`transactions.cash\_outs\[\].amount\` | Cash withdrawal amount. | | \`transactions.cash\_outs\[\].status\` | Cash withdrawal status. When canceled it is \`canceled\`. The possible values are \`created\`, \`canceled\`, \`processed\`, \`expired\`. | | \`transactions.cash\_outs\[\].status\_detail\` | Cash withdrawal status detail. | | \`items\[\].title\` | Item name. | | \`items\[\].unit\_price\` | Item unit price. | | \`items\[\].quantity\` | Item quantity. | | \`items\[\].unit\_measure\` | Item unit of measure. | | \`items\[\].external\_code\` | External item code (e.g., EAN). | | \`items\[\].external\_categories\[\].id\` | Item category identifier in the external system. | | \`discounts.payment\_methods\[\].new\_total\_amount\` | New total order amount when a discount is applied. | | \`discounts.payment\_methods\[\].type\` | Payment method to which the discount applies. | ::: :::AccordionComponent{title="Update error handling in cancellation" pill="2"} In cancellation, errors were grouped by type of change. See the tables below for more details. ### Errors that disappear The following errors exist in the Instore Orders V2 API but \*\*have no equivalent\*\* in the Orders API. | HTTP | Instore Orders V2 API | Note | |---|---|---| | \`400\` | \`invalid\_collector\_id\` | Removed in the Orders API. | | \`400\` | \`pos\_deleted\_error\` | Removed in the Orders API. | | \`401\` | \`unauthorized\` (authorization value not present) | Removed. Occurred when sending an alphanumeric \`{user\_id}\`. Identity is obtained directly from the Access Token. | | \`403\` | \`forbidden\` | Removed. Identity is validated internally by the Access Token. | ### Renamed errors The following errors were renamed in the Orders API. | HTTP | Instore Orders V2 API | Orders API | Note | |---|---|---|---| | \`400\` → \`404\` | \`pos\_obtainment\_by\_external\_id\_error\` and \`in\_store\_order\_delete\_error\` | \`order\_not\_found\` | In the Instore Orders V2 API, it was \`400\`. In the Orders API, it is \`404\`. Cancellation now operates on the \`order\_id\`. | | \`409\` | \`in\_store\_order\_delete\_error\` (Cannot delete a locked order) | \`instore\_order\_locked\_error\` | Same concept. The error code name changes. | ### Errors that change behavior The following errors exist in both APIs but with different behavior in the Orders API. | HTTP | Instore Orders V2 API | Orders API | Note | |---|---|---|---| | \`400\` → \`401\` | \`invalid\_access\_token\` | \`unauthorized\` | In the Instore Orders V2 API, it was \`400\`. In the Orders API, it is \`401\`. | ### Errors that remain unchanged The following error has the same behavior in both APIs. | HTTP | Error | Note | |---|---|---| | \`500\` | Generic | Internal error. Verify the response and retry the request. | ### Errors introduced by the Orders API The following errors have no equivalent in the Instore Orders V2 API. | HTTP | Error | Note | |---|---|---| | \`400\` | \`empty\_required\_header\` | \`X-Idempotency-Key\` is missing. | | \`400\` | \`invalid\_path\_param\` | The \`order\_id\` has an invalid format. | | \`401\` | \`unauthorized\` | Invalid or expired Access Token. | | \`404\` | \`order\_not\_found\` | The \`order\_id\` does not correspond to any created order. | | \`409\` | \`idempotency\_key\_already\_used\` | The \`X-Idempotency-Key\` has already been used with a different request. | | \`409\` | \`order\_already\_canceled\` | The order is already cancelled. Orders can only be cancelled with \`status: created\`. | ::: ## Implement refunds with the dedicated endpoint The Orders API introduces the dedicated endpoint :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/refund" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/refund-order/post"} for refunds, which did not exist in the Instore Orders V2 API. Previously, it was necessary to receive the payment webhook, obtain the \`payment\_id\`, and execute the refund through the Payments API separately. Now, the refund is performed directly on the order. :::::AccordionComponent{title="Implement the Orders API refund endpoint" pill="1"} > WARNING > > The Payments API \*\*must not be used\*\* in integrations with the Orders API. All operations, including refunds, must be carried out through the Orders API. To refund the full order amount, send the request to the :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/refund" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/refund-order/post"} endpoint without a body. \`\`\`curl curl -X POST \\ 'https://api.mercadopago.com/v1/orders/{{ORDER\_ID}}/refund' \\ -H 'Authorization: Bearer {{ACCESS\_TOKEN}}' \\ -H 'X-Idempotency-Key: {{IDEMPOTENCY\_KEY}}' \`\`\` Refunds are accepted up to 360 days from the payment date. For orders with cash withdrawal (\*cash-out\* or \*extracash\*), the maximum period is 72 hours. > SUCCESS\_MESSAGE > > See all available parameters in the :TagComponent{tag="API" text="Refund errors" href="/developers/en/docs/qr-code-ca/resources/refund-errors"}. ::::: :::AccordionComponent{title="Adapt refund response fields" pill="2"} The refund response varies according to the type of operation performed. | Field | Type | Description | |---|---|---| | \`id\` | \`string\` | Identifier of the refunded order. | | \`status\` | \`string\` | \`refunded\` for a full refund. | | \`status\_detail\` | \`string\` | \`refunded\` for a full refund. | | \`transactions.refunds\[\].id\` | \`string\` | Refund identifier, generated by Mercado Pago. | | \`transactions.refunds\[\].transaction\_id\` | \`string\` | Identifier of the payment transaction being refunded. | | \`transactions.refunds\[\].amount\` | \`string\` | Refund amount. If the payment was made with a discount, this reflects that amount. | | \`transactions.refunds\[\].status\` | \`string\` | \`processing\` when the refund has been requested and is being processed. | | \`transactions.refunds\[\].reference\_id\` | \`string\` | Identifier that associates the payment with its corresponding refund. | ::: ## Validate the migration After applying the changes, verify that the integration operates correctly across all flows before going to production. :::CheckboxComponent{label="X-Idempotency-Key header configured" defaultChecked="false"} The \`X-Idempotency-Key\` \*header\* must be present in all creation, cancellation, and refund operations. ::: :::CheckboxComponent{label="Order creation validated" defaultChecked="false"} Orders successfully created with the new \*payload\* at the :TagComponent{tag="API" text="POST /v1/orders" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/create-order/post"} endpoint and the \`id\` field captured from the \*response\*. ::: :::CheckboxComponent{label="Order query validated" defaultChecked="false"} Orders successfully queried via the :TagComponent{tag="API" text="GET /v1/orders/{order\_id}" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/get-order/get"} endpoint. ::: :::CheckboxComponent{label="Status webhook configured" defaultChecked="false"} Order status monitored via webhook with the \*\*"Order (Mercado Pago)"\*\* topic and the new values: \`created\`, \`processed\`, \`canceled\`, \`refunded\`, and \`expired\`. ::: :::CheckboxComponent{label="Order cancellation validated" defaultChecked="false"} Orders successfully canceled via the :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/cancel" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/cancel-order/post"} endpoint. ::: :::CheckboxComponent{label="Refunds validated" defaultChecked="false"} Refunds executed via the dedicated :TagComponent{tag="API" text="POST /v1/orders/{order\_id}/refund" href="/developers/en/reference/in-person-payments/qr-code-ca/orders/refund-order/post"} endpoint. ::: See the documentation to learn how to \[test the integration\](https://www.mercadopago.com.uy/developers/en/docs/qr-code/test-integration).