{
  "openapi": "3.1.0",
  "info": {
    "title": "MeuPedido Open Delivery API",
    "version": "1.4.0",
    "x-meupedido-api-revision": "2026-09-19",
    "summary": "Pedidos, cardápio e eventos da loja no padrão Open Delivery 1.4.0, com o MeuPedido no papel de aplicação de pedidos.",
    "description": "A API pública do MeuPedido implementa os módulos **Order** e **Merchant** do padrão\n[Open Delivery 1.4.0](https://www.opendelivery.org.br), com o MeuPedido no papel de\n**aplicação de pedidos**: o pedido nasce no MeuPedido (cardápio digital, app, mesa ou\nmarketplace integrado) e o seu sistema, tipicamente um PDV ou um hub, recebe o aviso, lê o\npedido e o conduz até o fim.\n\nO ciclo de uma integração é sempre o mesmo: obter um token, consultar os eventos pendentes,\nbuscar o pedido em `orderURL`, confirmar o recebimento do evento e avançar o pedido com as\nações de ciclo de vida. O guia passo a passo está em\n[Primeiros passos](https://developer.meupedido.io/pt-BR/docs/getting-started).\n\n## Ambiente\n\nHá um único ambiente, o de produção, em `https://api.meupedido.io/open-delivery`. Para\nexperimentar sem tocar uma loja real, use a credencial de uma\n[loja de teste](https://developer.meupedido.io/pt-BR/docs/getting-started/first-steps/test-store):\nos pedidos dela chegam com `test: true`.\n\n## Autenticação\n\nOAuth 2.0 `client_credentials`. Troque `client_id` e `client_secret` por um token em\n`POST /oauth/token` e envie `Authorization: Bearer <access_token>` em toda chamada. O token\nvale 3600 segundos e não há refresh token: peça outro com as mesmas credenciais. Os escopos\n(`orders:read`, `orders:write`, `merchant:read`, `catalog:read`) são definidos ao criar a\ncredencial e viajam no token.\n\n## Convenções\n\n- Datas em ISO 8601, UTC, com sufixo `Z`.\n- Valores monetários como `{ \"value\": 49.9, \"currency\": \"BRL\" }`.\n- Ids de pedido, evento e loja são GUIDs.\n- No pedido, campo sem valor vem como `null`; no envelope de evento, campo opcional sem valor\n  é omitido.\n- Erros das rotas de negócio: `{ \"error\": \"<código estável>\", \"message\": \"<texto em pt-BR>\" }`.\n  O endpoint de token usa o formato do RFC 6749 (`error` e `error_description`).\n- Só HTTPS, servidor a servidor. Chamadas de navegador são aceitas apenas a partir de\n  `https://developer.meupedido.io` (playground do portal).\n\n## Limites\n\n600 requisições por minuto por credencial nas rotas autenticadas; 60 por minuto por endereço\nIP no endpoint de token, mais 10 falhas de autenticação por minuto por credencial. Ao exceder,\n`429` com `Retry-After`.\n\n## Extensões\n\nO que não existe no padrão 1.4.0 está marcado com `x-meupedido-extension: true` e com a frase\n\"Extensão MeuPedido\" na descrição: as ações `startPreparation` e `pickedUp`, o parâmetro\n`limit` da consulta de eventos, o cabeçalho `Idempotency-Key`, os tipos de evento além dos seis\ndo padrão e a entrega por webhook.\n\n## Suporte\n\nPortal: [developer.meupedido.io](https://developer.meupedido.io). Atendimento pelo\nWhatsApp: [wa.me/5511955021289](https://wa.me/5511955021289).\n\n## Crédito\n\nEste contrato é derivado e adaptado da *Open Delivery API Specification* 1.4.0, publicada pela\nAbrasel Nacional sob a licença Apache 2.0\n([github.com/Abrasel-Nacional/opendelivery](https://github.com/Abrasel-Nacional/opendelivery)).\nDescrições de campos do padrão foram traduzidas e ajustadas ao comportamento real desta API.\n",
    "contact": {
      "name": "MeuPedido Developers",
      "url": "https://developer.meupedido.io"
    },
    "license": {
      "name": "Apache 2.0",
      "url": "https://www.apache.org/licenses/LICENSE-2.0"
    }
  },
  "servers": [
    {
      "url": "https://api.meupedido.io/open-delivery",
      "description": "Produção. Use a credencial da loja de teste para experimentar."
    }
  ],
  "security": [
    {
      "oauth2": [
        "orders:read",
        "orders:write",
        "merchant:read",
        "catalog:read"
      ]
    }
  ],
  "tags": [
    {
      "name": "authentication",
      "x-displayName": "Autenticação",
      "description": "Emissão do token de acesso (OAuth 2.0 `client_credentials`). É a única operação anônima da\nAPI, e por isso a única com limite por endereço IP em vez de por credencial.\n"
    },
    {
      "name": "merchant",
      "x-displayName": "Loja e cardápio",
      "description": "Dados cadastrais da loja e cardápio publicado, módulo Merchant do padrão. Somente leitura:\no lojista edita pelo painel e o seu sistema lê o resultado. O `merchantId` da rota precisa\nser o da credencial; qualquer outro responde `404`.\n"
    },
    {
      "name": "orders",
      "x-displayName": "Pedidos",
      "description": "Leitura do pedido completo e ações do ciclo de vida, módulo Order do padrão. Uma ação pela\nAPI percorre as mesmas regras que um clique no painel do lojista: mesma validação, mesmo\nhistórico, mesmos efeitos, com a credencial registrada como ator.\n"
    },
    {
      "name": "events",
      "x-displayName": "Eventos",
      "description": "Feed de eventos por polling e confirmação de recebimento. Um evento é o aviso de que algo\naconteceu com um pedido; ele nunca traz o pedido, que está em `orderURL`. Evento não\nconfirmado volta na próxima consulta (entrega at-least-once).\n"
    },
    {
      "name": "webhooks",
      "x-displayName": "Webhooks",
      "description": "Extensão MeuPedido: entrega por push do mesmo envelope do polling na URL HTTPS do lojista,\nassinada com HMAC-SHA256. Não é o `/v1/newEvent` do padrão 1.4.0. Receber por webhook não\nconfirma o evento: a confirmação continua sendo `POST /v1/events/acknowledgment`.\n"
    }
  ],
  "paths": {
    "/oauth/token": {
      "post": {
        "tags": [
          "authentication"
        ],
        "operationId": "oauthToken",
        "summary": "Obter token de acesso",
        "description": "Troca `client_id` e `client_secret` por um token de acesso. É o fluxo `client_credentials`\ndo OAuth 2.0 (RFC 6749): não há login de usuário, redirecionamento nem refresh token.\n\nAs credenciais podem ser enviadas de três formas, e o corpo em formulário é a canônica:\n\n- `application/x-www-form-urlencoded` com `grant_type`, `client_id` e `client_secret`.\n- `application/json` com os mesmos campos, em snake_case ou camelCase (`grantType`,\n  `clientId`, `clientSecret`).\n- `Authorization: Basic base64(client_id:client_secret)` com apenas `grant_type` no corpo.\n  Quando corpo e cabeçalho trazem credenciais, o corpo prevalece.\n\nA resposta traz cada campo em snake_case (RFC 6749) e em camelCase (texto do padrão Open\nDelivery), com valores idênticos. O token é um JWT HS256 com as claims `merchant_id`,\n`client_id` e `scope`, válido por 3600 segundos. Trate-o como opaco.\n\nRenove antes de expirar, com margem de alguns minutos, e compartilhe um token por\ncredencial entre os workers do seu processo: pedir um token por requisição esgota o\nlimite do endpoint.\n\nErros seguem o RFC 6749 (`error` e `error_description`). O `401 invalid_client` vem só com\no código, de propósito: credencial inexistente e segredo errado recebem a mesma resposta\npara o endpoint não revelar quais `client_id` existem. Se o endpoint responder\n`401 invalid_client` para uma credencial que funcionava, ela foi pausada ou revogada pelo\nlojista: pare e avise o operador.\n\nLimites: 60 requisições por minuto por endereço IP e 10 falhas de autenticação por minuto\npor credencial. O segredo correto continua sendo aceito mesmo com a credencial no limite de\nfalhas, para que ninguém derrube a sua integração conhecendo só o seu `client_id`. O corpo\né limitado a 4 KB. O caminho `POST /v1/oauth/token` é um alias que responde igual, mantido\npor compatibilidade; use o caminho canônico.\n",
        "security": [],
        "requestBody": {
          "required": true,
          "description": "Credenciais da integração. Também aceitas em `Authorization: Basic`, com apenas `grant_type` no corpo.",
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              },
              "example": {
                "grant_type": "client_credentials",
                "client_id": "mp_7f3a9c1e5b2d4a6f8e0c1b3d",
                "client_secret": "cole-aqui-o-segredo-mostrado-no-painel"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TokenRequest"
              },
              "example": {
                "grant_type": "client_credentials",
                "client_id": "mp_7f3a9c1e5b2d4a6f8e0c1b3d",
                "client_secret": "cole-aqui-o-segredo-mostrado-no-painel"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token emitido. Guarde `access_token` e o instante de expiração calculado a partir de `expires_in`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenResponse"
                },
                "example": {
                  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...",
                  "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ...",
                  "token_type": "bearer",
                  "tokenType": "bearer",
                  "expires_in": 3600,
                  "expiresIn": 3600,
                  "scope": "orders:read orders:write merchant:read catalog:read"
                }
              }
            }
          },
          "400": {
            "description": "Pedido inválido. `invalid_request` quando falta `grant_type`, `client_id` ou\n`client_secret`, quando o corpo não pôde ser lido no formato declarado ou quando o\ncabeçalho `Authorization: Basic` está malformado; `unsupported_grant_type` quando\n`grant_type` não é `client_credentials`. Corrija a requisição; repetir não resolve.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenError"
                },
                "examples": {
                  "invalid_request": {
                    "summary": "Faltam credenciais",
                    "value": {
                      "error": "invalid_request",
                      "error_description": "Informe client_id e client_secret."
                    }
                  },
                  "unsupported_grant_type": {
                    "summary": "grant_type não suportado",
                    "value": {
                      "error": "unsupported_grant_type",
                      "error_description": "Apenas client_credentials é suportado."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Credencial inexistente, segredo errado, ou credencial pausada ou revogada pelo\nlojista. Uma resposta só, sem `error_description`, de propósito. Não repita em loop:\nconfira o segredo e, se ele estava funcionando, avise o operador da loja.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenError"
                },
                "example": {
                  "error": "invalid_client"
                }
              }
            }
          },
          "413": {
            "description": "Corpo acima de 4 KB. Um pedido de token tem só três campos e cabe em 200 bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenError"
                },
                "example": {
                  "error": "invalid_request",
                  "error_description": "O corpo excede 4096 bytes. Um pedido de token tem só grant_type, client_id e client_secret."
                }
              }
            }
          },
          "415": {
            "description": "`Content-Type` diferente de `application/x-www-form-urlencoded` e `application/json`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TokenError"
                },
                "example": {
                  "error": "invalid_request",
                  "error_description": "Content-Type não suportado. Use application/x-www-form-urlencoded ou application/json."
                }
              }
            }
          },
          "429": {
            "description": "Limite atingido. Dois limites independentes produzem esta resposta: 60 requisições\npor minuto por endereço IP (corpo com `error` e `message`, escrito pelo limitador\nglobal) e 10 falhas de autenticação por minuto por credencial (corpo no formato do\nRFC 6749). Nos dois casos, espere o `Retry-After` antes de tentar de novo. No segundo,\nconfira o segredo: o correto continua sendo aceito.\n",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "anyOf": [
                    {
                      "$ref": "#/components/schemas/TokenError"
                    },
                    {
                      "$ref": "#/components/schemas/RateLimitError"
                    }
                  ]
                },
                "examples": {
                  "too_many_failures": {
                    "summary": "Dez falhas na mesma credencial em um minuto",
                    "value": {
                      "error": "rate_limit_exceeded",
                      "error_description": "Muitas tentativas para esta credencial."
                    }
                  },
                  "too_many_requests": {
                    "summary": "Sessenta requisições do mesmo endereço em um minuto",
                    "value": {
                      "error": "rate_limit_exceeded",
                      "message": "Limite de requisições excedido."
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/oauth/token\" \\\n  -H \"Content-Type: application/x-www-form-urlencoded\" \\\n  -d \"grant_type=client_credentials\" \\\n  -d \"client_id=mp_7f3a9c1e5b2d4a6f8e0c1b3d\" \\\n  -d \"client_secret=$CLIENT_SECRET\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch('https://api.meupedido.io/open-delivery/oauth/token', {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n  body: new URLSearchParams({\n    grant_type: 'client_credentials',\n    client_id: 'mp_7f3a9c1e5b2d4a6f8e0c1b3d',\n    client_secret: process.env.CLIENT_SECRET,\n  }),\n});\n\nconst token = await response.json();\n// token.access_token vai em \"Authorization: Bearer\" nas próximas chamadas.\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import os\nimport requests\n\nresponse = requests.post(\n    \"https://api.meupedido.io/open-delivery/oauth/token\",\n    data={\n        \"grant_type\": \"client_credentials\",\n        \"client_id\": \"mp_7f3a9c1e5b2d4a6f8e0c1b3d\",\n        \"client_secret\": os.environ[\"CLIENT_SECRET\"],\n    },\n    timeout=10,\n)\nresponse.raise_for_status()\ntoken = response.json()\n# token[\"access_token\"] vai em \"Authorization: Bearer\" nas próximas chamadas.\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "using var http = new HttpClient();\n\nusing var content = new FormUrlEncodedContent(new Dictionary<string, string>\n{\n    [\"grant_type\"] = \"client_credentials\",\n    [\"client_id\"] = \"mp_7f3a9c1e5b2d4a6f8e0c1b3d\",\n    [\"client_secret\"] = Environment.GetEnvironmentVariable(\"CLIENT_SECRET\")!,\n});\n\nusing var response = await http.PostAsync(\"https://api.meupedido.io/open-delivery/oauth/token\", content);\nresponse.EnsureSuccessStatusCode();\n\nvar token = await response.Content.ReadFromJsonAsync<JsonElement>();\nvar accessToken = token.GetProperty(\"access_token\").GetString();\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$ch = curl_init('https://api.meupedido.io/open-delivery/oauth/token');\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded'],\n    CURLOPT_POSTFIELDS => http_build_query([\n        'grant_type' => 'client_credentials',\n        'client_id' => 'mp_7f3a9c1e5b2d4a6f8e0c1b3d',\n        'client_secret' => getenv('CLIENT_SECRET'),\n    ]),\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$token = json_decode(curl_exec($ch), true);\ncurl_close($ch);\n// $token['access_token'] vai em \"Authorization: Bearer\" nas próximas chamadas.\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "HttpClient client = HttpClient.newHttpClient();\n\nString form = \"grant_type=client_credentials\"\n    + \"&client_id=\" + URLEncoder.encode(\"mp_7f3a9c1e5b2d4a6f8e0c1b3d\", StandardCharsets.UTF_8)\n    + \"&client_secret=\" + URLEncoder.encode(System.getenv(\"CLIENT_SECRET\"), StandardCharsets.UTF_8);\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/oauth/token\"))\n    .header(\"Content-Type\", \"application/x-www-form-urlencoded\")\n    .POST(HttpRequest.BodyPublishers.ofString(form))\n    .build();\n\nHttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());\n// O JSON em response.body() traz access_token e expires_in.\n"
          }
        ]
      }
    },
    "/v1/merchant/{merchantId}": {
      "get": {
        "tags": [
          "merchant"
        ],
        "operationId": "getMerchant",
        "summary": "Buscar loja",
        "description": "Dados cadastrais da loja ligada à credencial: nome, documento, status, contatos, endereço\ne modalidades de atendimento (`services`). Somente leitura.\n\nO `merchantId` da rota precisa ser o da credencial. Ele está na claim `merchant_id` do\ntoken e em `merchant.id` de todo pedido. Qualquer outro id, mesmo de uma loja que exista,\nresponde `404` no formato ProblemDetails, sem distinção: a API não revela quais lojas\nexistem. Um id que não é GUID responde o mesmo `404`.\n\n`status` é `UNAVAILABLE` quando a loja está desativada ou fechada temporariamente. É status\nde loja, não ausência de loja: mostre \"fechado agora\" em vez de sumir com o restaurante.\nCampo sem valor vem como `null`. Para saber se algo mudou, compare `lastUpdate`.\n",
        "security": [
          {
            "oauth2": [
              "merchant:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          }
        ],
        "responses": {
          "200": {
            "description": "A loja da credencial.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Merchant"
                },
                "example": {
                  "id": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d",
                  "name": "Loja de teste Acme",
                  "description": "Pizzas de fermentação natural, entrega e retirada.",
                  "document": "12345678000190",
                  "status": "AVAILABLE",
                  "contactEmails": [
                    "contato@acme.example"
                  ],
                  "contactPhones": [
                    "1140041234"
                  ],
                  "address": {
                    "country": "BR",
                    "state": "SP",
                    "city": "São Paulo",
                    "district": "Pinheiros",
                    "street": "Rua dos Pinheiros",
                    "number": "1000",
                    "postalCode": "05422001",
                    "complement": null,
                    "latitude": -23.5656,
                    "longitude": -46.6898
                  },
                  "services": [
                    {
                      "serviceType": "DELIVERY",
                      "status": "AVAILABLE",
                      "menuId": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
                    },
                    {
                      "serviceType": "TAKEOUT",
                      "status": "AVAILABLE",
                      "menuId": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
                    }
                  ],
                  "createdAt": "2025-03-12T14:02:11Z",
                  "lastUpdate": "2026-09-18T21:45:09Z"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenMerchantRead"
          },
          "404": {
            "$ref": "#/components/responses/MerchantNotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.meupedido.io/open-delivery/v1/merchant/6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const merchantId = '6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/merchant/${merchantId}`,\n  { headers: { Authorization: `Bearer ${accessToken}` } },\n);\n\nif (!response.ok) throw new Error(`merchant: HTTP ${response.status}`);\nconst merchant = await response.json();\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\nmerchant_id = \"6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\"\n\nresponse = requests.get(\n    f\"https://api.meupedido.io/open-delivery/v1/merchant/{merchant_id}\",\n    headers={\"Authorization\": f\"Bearer {access_token}\"},\n    timeout=10,\n)\nresponse.raise_for_status()\nmerchant = response.json()\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var merchantId = \"6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var response = await http.GetAsync($\"v1/merchant/{merchantId}\");\nresponse.EnsureSuccessStatusCode();\n\nvar merchant = await response.Content.ReadFromJsonAsync<JsonElement>();\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$merchantId = '6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/merchant/{$merchantId}\");\ncurl_setopt_array($ch, [\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer {$accessToken}\"],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$merchant = json_decode(curl_exec($ch), true);\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String merchantId = \"6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/merchant/\" + merchantId))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .GET()\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// response.body() é o JSON da loja.\n"
          }
        ]
      }
    },
    "/v1/merchant/{merchantId}/menus": {
      "get": {
        "tags": [
          "merchant"
        ],
        "operationId": "getMenus",
        "summary": "Buscar cardápio",
        "description": "O cardápio publicado da loja, completo, em uma resposta só: categorias, itens e grupos de\nopções. O padrão prevê que uma loja tenha mais de um cardápio, por isso a resposta é um\narray; no MeuPedido cada loja tem um único cardápio, então o array traz sempre um elemento,\ncujo `id` é o `menuId` referenciado em `services` da loja.\n\nO cardápio é normalizado: categorias apontam para itens por id (`itemOfferIds`), itens\napontam para grupos de opções por id (`optionGroupIds`). Monte índices por id em memória\nantes de percorrer.\n\nDuas traduções merecem atenção. Um produto com variações de tamanho vira um item mais um\ngrupo obrigatório de escolha única chamado **Tamanho**, com id `{productId}-variacoes`,\nem que cada opção é uma variação com o próprio preço, da mais barata para a mais cara; o\n`price` do item é o da primeira opção desse grupo, ou seja, o da variação mais barata. E\nitem pausado é enviado como `UNAVAILABLE`, nunca omitido: os ids ficam estáveis e um\npedido antigo sempre aponta para um item que existe.\n\nNão existe evento de cardápio. Para manter uma cópia local, busque em um intervalo que\nfaça sentido para a sua operação; cada chamada conta no limite de 600 por minuto. O\n`merchantId` segue a mesma regra de `getMerchant`: precisa ser o da credencial.\n",
        "security": [
          {
            "oauth2": [
              "catalog:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MerchantId"
          }
        ],
        "responses": {
          "200": {
            "description": "Lista com o cardápio da loja. Sempre um elemento.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Menu"
                  }
                },
                "example": [
                  {
                    "id": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d",
                    "name": "Cardápio Loja de teste Acme",
                    "categories": [
                      {
                        "id": "5b7e1f3a-9c2d-4e6b-8a1f-0d3c7e9b2a54",
                        "name": "Pizzas",
                        "description": "Massa de fermentação natural, 35 cm ou 25 cm.",
                        "index": 0,
                        "status": "AVAILABLE",
                        "itemOfferIds": [
                          "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d"
                        ]
                      }
                    ],
                    "items": [
                      {
                        "id": "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d",
                        "name": "Pizza Margherita",
                        "description": "Molho de tomate, mussarela de búfala e manjericão.",
                        "externalCode": "PZ-001",
                        "status": "AVAILABLE",
                        "image": "https://cdn.meupedido.io/lojas/acme/pizza-margherita.jpg",
                        "price": {
                          "value": 39.9,
                          "originalValue": null,
                          "currency": "BRL"
                        },
                        "optionGroupIds": [
                          "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d-variacoes",
                          "2c8e4b1d-7f3a-4d9e-a5c6-1b0f8d3e7a92"
                        ]
                      }
                    ],
                    "optionGroups": [
                      {
                        "id": "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d-variacoes",
                        "name": "Tamanho",
                        "description": null,
                        "index": 0,
                        "status": "AVAILABLE",
                        "minPermitted": 1,
                        "maxPermitted": 1,
                        "options": [
                          {
                            "id": "9e3b5d7f-2c1a-4f6e-8d4b-6a0c2f9e1b58",
                            "name": "Média",
                            "externalCode": "9e3b5d7f-2c1a-4f6e-8d4b-6a0c2f9e1b58",
                            "index": 0,
                            "status": "AVAILABLE",
                            "price": {
                              "value": 39.9,
                              "originalValue": null,
                              "currency": "BRL"
                            }
                          },
                          {
                            "id": "4d1a7c3e-8b5f-4e2a-9c6d-3f0b1e8a5d27",
                            "name": "Grande",
                            "externalCode": "4d1a7c3e-8b5f-4e2a-9c6d-3f0b1e8a5d27",
                            "index": 1,
                            "status": "AVAILABLE",
                            "price": {
                              "value": 49.9,
                              "originalValue": null,
                              "currency": "BRL"
                            }
                          }
                        ]
                      },
                      {
                        "id": "2c8e4b1d-7f3a-4d9e-a5c6-1b0f8d3e7a92",
                        "name": "Adicionais",
                        "description": "Escolha até 3.",
                        "index": 1,
                        "status": "AVAILABLE",
                        "minPermitted": 0,
                        "maxPermitted": 3,
                        "options": [
                          {
                            "id": "b8a7c6d5-e4f3-4210-9876-543210fedcba",
                            "name": "Borda recheada",
                            "externalCode": "b8a7c6d5-e4f3-4210-9876-543210fedcba",
                            "index": 0,
                            "status": "AVAILABLE",
                            "price": {
                              "value": 8,
                              "originalValue": null,
                              "currency": "BRL"
                            }
                          }
                        ]
                      }
                    ]
                  }
                ]
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenCatalogRead"
          },
          "404": {
            "$ref": "#/components/responses/MerchantNotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.meupedido.io/open-delivery/v1/merchant/6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d/menus\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const merchantId = '6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/merchant/${merchantId}/menus`,\n  { headers: { Authorization: `Bearer ${accessToken}` } },\n);\n\nif (!response.ok) throw new Error(`menus: HTTP ${response.status}`);\nconst [menu] = await response.json();\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\nmerchant_id = \"6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\"\n\nresponse = requests.get(\n    f\"https://api.meupedido.io/open-delivery/v1/merchant/{merchant_id}/menus\",\n    headers={\"Authorization\": f\"Bearer {access_token}\"},\n    timeout=10,\n)\nresponse.raise_for_status()\n(menu,) = response.json()\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var merchantId = \"6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var response = await http.GetAsync($\"v1/merchant/{merchantId}/menus\");\nresponse.EnsureSuccessStatusCode();\n\nvar menus = await response.Content.ReadFromJsonAsync<JsonElement>();\nvar menu = menus[0];\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$merchantId = '6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/merchant/{$merchantId}/menus\");\ncurl_setopt_array($ch, [\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer {$accessToken}\"],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n[$menu] = json_decode(curl_exec($ch), true);\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String merchantId = \"6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/merchant/\" + merchantId + \"/menus\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .GET()\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// response.body() é um array JSON com um cardápio.\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}": {
      "get": {
        "tags": [
          "orders"
        ],
        "operationId": "getOrder",
        "summary": "Buscar pedido",
        "description": "O pedido completo, no formato do padrão Open Delivery 1.4.0. É o mesmo corpo para qualquer\norigem: app, site, cardápio digital, mesa ou marketplace produzem a mesma estrutura, com a\norigem exposta em `extraInfo` (`origem=`, `canal=`, `numeroCanal=`).\n\nÉ o que você lê depois de receber um evento `CREATED` ou `MODIFIED`: o envelope traz só\n`orderId` e `orderURL`, e esta operação devolve o pedido de agora, e não uma fotografia do\ninstante da transição. Use `id` como chave; `displayId` é o número curto que a loja e o\ncliente veem e se repete ao longo do tempo.\n\nRegras do formato: campo sem valor vem como `null` (a chave está sempre lá); datas em UTC\ncom `Z`; dinheiro como `{ value, currency }`, copiado do que a loja cobrou, nunca\nrecalculado. `delivery` só existe em pedido `DELIVERY`; `takeout`, em `TAKEOUT` e `INDOOR`;\n`schedule`, em pedido `SCHEDULED`. `test: true` marca pedido de loja de teste: não o leve\npara o seu faturamento.\n\nPedido inexistente e pedido de outra loja respondem o mesmo `404 order_not_found`, sem\ndistinção, para a rota não revelar quais ids existem. Um `orderId` que não é GUID responde\n`404` no formato ProblemDetails do ASP.NET.\n",
        "security": [
          {
            "oauth2": [
              "orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          }
        ],
        "responses": {
          "200": {
            "description": "O pedido, no formato do padrão.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Order"
                },
                "examples": {
                  "delivery": {
                    "$ref": "#/components/examples/OrderDelivery"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersRead"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}`,\n  { headers: { Authorization: `Bearer ${accessToken}` } },\n);\n\nif (response.status === 404) {\n  // { error: 'order_not_found', message: '...' }: não pertence à sua loja ou não existe.\n}\nconst order = await response.json();\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.get(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}\",\n    headers={\"Authorization\": f\"Bearer {access_token}\"},\n    timeout=10,\n)\nresponse.raise_for_status()\norder = response.json()\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var response = await http.GetAsync($\"v1/orders/{orderId}\");\nresponse.EnsureSuccessStatusCode();\n\nvar order = await response.Content.ReadFromJsonAsync<JsonElement>();\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}\");\ncurl_setopt_array($ch, [\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer {$accessToken}\"],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$order = json_decode(curl_exec($ch), true);\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .GET()\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// response.body() é o JSON do pedido.\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/confirm": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "confirmOrder",
        "summary": "Confirmar pedido",
        "description": "A loja aceitou o pedido. A situação passa a `ACCEPTED`, o lojista e o cliente veem a\nmudança e um evento `CONFIRMED` entra no feed. É a primeira ação de todo pedido: um\npedido `PENDING` só sai dali por `confirm` ou por cancelamento.\n\nA resposta é `202` com `status: \"accepted\"` quando a transição foi aplicada, ou\n`status: \"already_applied\"` quando o pedido já estava em `ACCEPTED`. O segundo caso não é\nerro, de propósito: é o comando reenviado depois de um timeout, e tratá-lo como falha\nfaria o seu sistema mostrar erro numa operação que já está feita. Em pedido que já\navançou (`PREPARING`, `READY`, `DELIVERY`), `confirm` é um retrocesso: é aceito, devolve\no pedido a `ACCEPTED` e não gera evento. O mesmo vale a partir de `DONE`: a API pode\nreabrir um pedido concluído para corrigir um engano, como o painel faz.\n\nEnvie `Idempotency-Key` desde o início: a resposta da primeira execução é gravada por 24\nhoras e devolvida em toda repetição com a mesma chave. A chave tem no máximo 128\ncaracteres; acima disso a resposta é `400 invalid_idempotency_key` e nada é executado.\nMesma chave com outra rota ou outro corpo responde `409`. `422 invalid_transition` (em\npedido `CANCELLED`, que é final) é bug de fluxo, não erro transitório: repetir não resolve.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Transição aplicada, ou já aplicada antes. `situation` é a situação atual do pedido.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Aplicada agora",
                    "value": {
                      "status": "accepted",
                      "situation": "ACCEPTED"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava aceito",
                    "value": {
                      "status": "already_applied",
                      "situation": "ACCEPTED"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidIdempotencyKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "422": {
            "description": "A transição não é permitida a partir da situação atual. `message` explica em pt-BR. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "Pedido cancelado não muda mais de situação."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/confirm\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: confirm-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/confirm`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `confirm-${orderId}`,\n    },\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'ACCEPTED' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/confirm\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"confirm-{order_id}\",\n    },\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"ACCEPTED\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/confirm\");\nrequest.Headers.Add(\"Idempotency-Key\", $\"confirm-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"ACCEPTED\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/confirm\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: confirm-{$orderId}\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'ACCEPTED']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/confirm\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"confirm-\" + orderId)\n    .POST(HttpRequest.BodyPublishers.noBody())\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"ACCEPTED\"}\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/startPreparation": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "startPreparation",
        "summary": "Iniciar preparo",
        "x-meupedido-extension": true,
        "description": "Extensão MeuPedido: não existe no padrão 1.4.0.\n\nA cozinha começou. A situação passa a `PREPARING` e um evento `PREPARING` entra no feed.\nÉ opcional: um pedido `ACCEPTED` pode ir direto para `READY`, `DELIVERY` ou `DONE`. Existe\nporque o PDV que integra conosco precisa dizer à tela da loja e ao cliente que o pedido\nestá em produção, e o padrão não descreve esse fato.\n\nSó é aceito a partir de `ACCEPTED` (ou como retrocesso a partir de `READY` e `DELIVERY`,\nsem evento). Em pedido `PENDING` responde `422`: confirme primeiro. Mesma semântica de\n`202 accepted` e `already_applied`, `Idempotency-Key`, `400`, `409` e `422` de\n`confirmOrder`.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Transição aplicada, ou já aplicada antes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Aplicada agora",
                    "value": {
                      "status": "accepted",
                      "situation": "PREPARING"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava em preparo",
                    "value": {
                      "status": "already_applied",
                      "situation": "PREPARING"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidIdempotencyKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "422": {
            "description": "A transição não é permitida a partir da situação atual. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "Não existe passo de 'PENDING' para 'PREPARING'."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/startPreparation\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: startPreparation-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/startPreparation`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `startPreparation-${orderId}`,\n    },\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'PREPARING' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/startPreparation\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"startPreparation-{order_id}\",\n    },\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"PREPARING\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/startPreparation\");\nrequest.Headers.Add(\"Idempotency-Key\", $\"startPreparation-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"PREPARING\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/startPreparation\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: startPreparation-{$orderId}\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'PREPARING']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/startPreparation\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"startPreparation-\" + orderId)\n    .POST(HttpRequest.BodyPublishers.noBody())\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"PREPARING\"}\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/readyForPickup": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "readyForPickup",
        "summary": "Pronto para retirada",
        "description": "O pedido está pronto: para sair para entrega, para o cliente retirar ou para ser servido\nna mesa. A situação passa a `READY` e um evento `READY_FOR_PICKUP` entra no feed.\n\nAceito a partir de `ACCEPTED` e `PREPARING`; como retrocesso, a partir de `DELIVERY` e\nde `DONE` (sem evento). Em pedido `PENDING` responde `422`: confirme primeiro. Em pedido\n`CANCELLED` responde `422`. Mesma semântica de `202 accepted` e `already_applied`,\n`Idempotency-Key`, `400`, `409` e `422` de `confirmOrder`.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Transição aplicada, ou já aplicada antes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Aplicada agora",
                    "value": {
                      "status": "accepted",
                      "situation": "READY"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava pronto",
                    "value": {
                      "status": "already_applied",
                      "situation": "READY"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidIdempotencyKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "422": {
            "description": "A transição não é permitida a partir da situação atual. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "Não existe passo de 'PENDING' para 'READY'."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/readyForPickup\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: readyForPickup-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/readyForPickup`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `readyForPickup-${orderId}`,\n    },\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'READY' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/readyForPickup\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"readyForPickup-{order_id}\",\n    },\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"READY\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/readyForPickup\");\nrequest.Headers.Add(\"Idempotency-Key\", $\"readyForPickup-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"READY\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/readyForPickup\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: readyForPickup-{$orderId}\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'READY']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/readyForPickup\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"readyForPickup-\" + orderId)\n    .POST(HttpRequest.BodyPublishers.noBody())\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"READY\"}\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/dispatch": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "dispatchOrder",
        "summary": "Despachar pedido",
        "description": "O pedido saiu para entrega. A situação passa a `DELIVERY` e um evento `DISPATCHED` entra\nno feed. Só faz sentido em pedido do tipo `DELIVERY`: em pedido `INDOOR` responde `422`,\nporque a situação não existe para mesa (a mensagem cita o nome interno do tipo, `TABLE`);\nem `TAKEOUT`, prefira `readyForPickup` seguido de `pickedUp`.\n\nAceito a partir de `ACCEPTED`, `PREPARING` e `READY`. Em pedido `PENDING` responde `422`:\nconfirme primeiro. Mesma semântica de `202 accepted` e `already_applied`,\n`Idempotency-Key`, `400`, `409` e `422` de `confirmOrder`.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Transição aplicada, ou já aplicada antes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Aplicada agora",
                    "value": {
                      "status": "accepted",
                      "situation": "DELIVERY"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava em entrega",
                    "value": {
                      "status": "already_applied",
                      "situation": "DELIVERY"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidIdempotencyKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "422": {
            "description": "A transição não é permitida a partir da situação atual ou para este tipo de pedido. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "A situação 'DELIVERY' não existe em pedido do tipo 'TABLE'."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/dispatch\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: dispatch-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/dispatch`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `dispatch-${orderId}`,\n    },\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'DELIVERY' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/dispatch\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"dispatch-{order_id}\",\n    },\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"DELIVERY\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/dispatch\");\nrequest.Headers.Add(\"Idempotency-Key\", $\"dispatch-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"DELIVERY\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/dispatch\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: dispatch-{$orderId}\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'DELIVERY']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/dispatch\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"dispatch-\" + orderId)\n    .POST(HttpRequest.BodyPublishers.noBody())\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"DELIVERY\"}\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/pickedUp": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "pickedUp",
        "summary": "Pedido retirado",
        "x-meupedido-extension": true,
        "description": "Extensão MeuPedido: não existe no padrão 1.4.0.\n\nO cliente retirou o pedido no balcão, ou foi servido na mesa. Encerra o pedido: a situação\npassa a `DONE` e um evento `CONCLUDED` entra no feed. É o fechamento natural de pedido\n`TAKEOUT` e `INDOOR`, cujo caminho termina em `readyForPickup` seguido de `pickedUp`.\n\n`pickedUp` e `delivered` levam à mesma situação, porque para a loja os dois significam\npedido encerrado. Use o que descreve o que aconteceu. Aceito a partir de `ACCEPTED`,\n`PREPARING`, `READY` e `DELIVERY`; em pedido `PENDING` ou `CANCELLED` responde `422`.\n`DONE` é final para avanço: só um retrocesso corretivo (`confirm`, `startPreparation`,\n`readyForPickup`, `dispatch`) ou uma decisão externa (estorno, canal) o tira dali;\n`requestCancellation` responde `422`.\nMesma semântica de `202 accepted` e `already_applied`, `Idempotency-Key`, `400`, `409` e\n`422` de `confirmOrder`.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Transição aplicada, ou já aplicada antes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Aplicada agora",
                    "value": {
                      "status": "accepted",
                      "situation": "DONE"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava encerrado",
                    "value": {
                      "status": "already_applied",
                      "situation": "DONE"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidIdempotencyKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "422": {
            "description": "A transição não é permitida a partir da situação atual. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "Pedido cancelado não muda mais de situação."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/pickedUp\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: pickedUp-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/pickedUp`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `pickedUp-${orderId}`,\n    },\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'DONE' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/pickedUp\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"pickedUp-{order_id}\",\n    },\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"DONE\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/pickedUp\");\nrequest.Headers.Add(\"Idempotency-Key\", $\"pickedUp-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"DONE\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/pickedUp\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: pickedUp-{$orderId}\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'DONE']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/pickedUp\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"pickedUp-\" + orderId)\n    .POST(HttpRequest.BodyPublishers.noBody())\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"DONE\"}\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/delivered": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "deliverOrder",
        "summary": "Pedido entregue",
        "description": "O pedido foi entregue ao cliente. Encerra o pedido: a situação passa a `DONE` e um evento\n`CONCLUDED` entra no feed. O evento é `CONCLUDED`, e não `DELIVERED`, porque o MeuPedido\ntem um único estado final, válido para entrega e retirada; afirmar uma entrega num pedido\nretirado seria mentir num campo que o seu sistema usa para decidir.\n\nAceito a partir de `ACCEPTED`, `PREPARING`, `READY` e `DELIVERY`; em pedido `PENDING` ou\n`CANCELLED` responde `422`. Mesma semântica de `202 accepted` e `already_applied`,\n`Idempotency-Key`, `400`, `409` e `422` de `confirmOrder`.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Transição aplicada, ou já aplicada antes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Aplicada agora",
                    "value": {
                      "status": "accepted",
                      "situation": "DONE"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava encerrado",
                    "value": {
                      "status": "already_applied",
                      "situation": "DONE"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidIdempotencyKey"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "422": {
            "description": "A transição não é permitida a partir da situação atual. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "Não existe passo de 'PENDING' para 'DONE'."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/delivered\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: delivered-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/delivered`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `delivered-${orderId}`,\n    },\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'DONE' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/delivered\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"delivered-{order_id}\",\n    },\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"DONE\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/delivered\");\nrequest.Headers.Add(\"Idempotency-Key\", $\"delivered-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"DONE\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/delivered\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: delivered-{$orderId}\",\n    ],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'DONE']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/delivered\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"delivered-\" + orderId)\n    .POST(HttpRequest.BodyPublishers.noBody())\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"DONE\"}\n"
          }
        ]
      }
    },
    "/v1/orders/{orderId}/requestCancellation": {
      "post": {
        "tags": [
          "orders"
        ],
        "operationId": "requestCancellation",
        "summary": "Cancelar pedido",
        "description": "Cancela o pedido **imediatamente**. Apesar do nome herdado do padrão, não existe etapa de\naprovação: a situação passa a `CANCELLED` na hora, a resposta já vem com\n`situation: \"CANCELLED\"` e um evento `CANCELLED` entra no feed, com `metadata.reason`\nigual ao motivo informado e `metadata.code` igual a `OTHER_CANCELLATION_REASON`\n(cancelamento pela loja ou pela API). No padrão 1.4.0 esta operação é um pedido de\ncancelamento que o outro lado aceita ou nega; `acceptCancellation` e `denyCancellation`\nnão existem aqui porque não há nada a aprovar.\n\nO corpo é opcional. `reason` é registrado no histórico do pedido e mostrado ao lojista;\nsem ele, o motivo é \"Cancelamento solicitado pela API pública.\". São no máximo 500\ncaracteres: acima disso a resposta é `400 invalid_cancellation_reason` e o pedido **não**\né cancelado. `code` é aceito por compatibilidade com o padrão e **ignorado**: o `code` do\nevento `CANCELLED` deriva de quem cancelou, não deste campo. Enviar sem corpo e sem\n`Content-Type` também funciona.\n\nAceito em qualquer situação, exceto `DONE` (`422`: pedido concluído não é cancelado por\naqui). `CANCELLED` é final: nenhuma ação tira o pedido dessa situação. Cancelar de novo\nresponde `202 already_applied`. Mesma semântica de `Idempotency-Key`, `400`, `409` e `422`\nde `confirmOrder`.\n",
        "security": [
          {
            "oauth2": [
              "orders:write"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/OrderId"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": false,
          "description": "Motivo do cancelamento. Opcional; sem corpo, o motivo padrão é registrado.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CancellationRequest"
              },
              "example": {
                "reason": "Produto em falta.",
                "code": "OTHER_CANCELLATION_REASON"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Pedido cancelado, ou já estava cancelado.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TransitionResult"
                },
                "examples": {
                  "accepted": {
                    "summary": "Cancelado agora",
                    "value": {
                      "status": "accepted",
                      "situation": "CANCELLED"
                    }
                  },
                  "already_applied": {
                    "summary": "O pedido já estava cancelado",
                    "value": {
                      "status": "already_applied",
                      "situation": "CANCELLED"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "`invalid_cancellation_reason` (`Content-Type: application/json`): `reason` passou de 500\ncaracteres, o tamanho que o histórico do pedido grava. `invalid_idempotency_key`: a\n`Idempotency-Key` passou de 128. Corpo que não é JSON válido para esta operação responde\nno formato ValidationProblemDetails do ASP.NET, com `Content-Type: application/problem+json`\ne `errors` apontando o campo. Nos três casos o pedido continua como estava; corrija a\nrequisição, repetir não muda a resposta.\n",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "examples": {
                  "invalid_cancellation_reason": {
                    "summary": "Motivo acima de 500 caracteres",
                    "value": {
                      "error": "invalid_cancellation_reason",
                      "message": "O motivo do cancelamento tem no máximo 500 caracteres."
                    }
                  },
                  "invalid_idempotency_key": {
                    "summary": "Chave de idempotência acima de 128 caracteres",
                    "value": {
                      "error": "invalid_idempotency_key",
                      "message": "A chave de idempotência tem no máximo 128 caracteres."
                    }
                  }
                }
              },
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                },
                "example": {
                  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
                  "title": "One or more validation errors occurred.",
                  "status": 400,
                  "errors": {
                    "$": [
                      "'<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
                    ]
                  },
                  "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersWrite"
          },
          "404": {
            "$ref": "#/components/responses/OrderNotFound"
          },
          "409": {
            "$ref": "#/components/responses/IdempotencyKeyReuse"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "422": {
            "description": "O pedido está em `DONE` e não pode ser cancelado por aqui. Não repita.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiError"
                },
                "example": {
                  "error": "invalid_transition",
                  "message": "Pedido concluído não é cancelado pelo painel. Use estorno."
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e/requestCancellation\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Idempotency-Key: cancel-9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{ \"reason\": \"Produto em falta.\", \"code\": \"OTHER_CANCELLATION_REASON\" }'\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\nconst response = await fetch(\n  `https://api.meupedido.io/open-delivery/v1/orders/${orderId}/requestCancellation`,\n  {\n    method: 'POST',\n    headers: {\n      Authorization: `Bearer ${accessToken}`,\n      'Idempotency-Key': `cancel-${orderId}`,\n      'Content-Type': 'application/json',\n    },\n    body: JSON.stringify({ reason: 'Produto em falta.', code: 'OTHER_CANCELLATION_REASON' }),\n  },\n);\n\nconst result = await response.json(); // { status: 'accepted', situation: 'CANCELLED' }\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\norder_id = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\"\n\nresponse = requests.post(\n    f\"https://api.meupedido.io/open-delivery/v1/orders/{order_id}/requestCancellation\",\n    headers={\n        \"Authorization\": f\"Bearer {access_token}\",\n        \"Idempotency-Key\": f\"cancel-{order_id}\",\n    },\n    json={\"reason\": \"Produto em falta.\", \"code\": \"OTHER_CANCELLATION_REASON\"},\n    timeout=15,\n)\nresult = response.json()  # {\"status\": \"accepted\", \"situation\": \"CANCELLED\"}\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "var orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\n\nusing var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nusing var request = new HttpRequestMessage(HttpMethod.Post, $\"v1/orders/{orderId}/requestCancellation\")\n{\n    Content = JsonContent.Create(new { reason = \"Produto em falta.\", code = \"OTHER_CANCELLATION_REASON\" }),\n};\nrequest.Headers.Add(\"Idempotency-Key\", $\"cancel-{orderId}\");\n\nusing var response = await http.SendAsync(request);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\n// { \"status\": \"accepted\", \"situation\": \"CANCELLED\" }\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$orderId = '9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e';\n\n$ch = curl_init(\"https://api.meupedido.io/open-delivery/v1/orders/{$orderId}/requestCancellation\");\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        \"Idempotency-Key: cancel-{$orderId}\",\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode(['reason' => 'Produto em falta.', 'code' => 'OTHER_CANCELLATION_REASON']),\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['status' => 'accepted', 'situation' => 'CANCELLED']\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String orderId = \"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\";\nString body = \"{\\\"reason\\\":\\\"Produto em falta.\\\",\\\"code\\\":\\\"OTHER_CANCELLATION_REASON\\\"}\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/orders/\" + orderId + \"/requestCancellation\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Idempotency-Key\", \"cancel-\" + orderId)\n    .header(\"Content-Type\", \"application/json\")\n    .POST(HttpRequest.BodyPublishers.ofString(body))\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"status\":\"accepted\",\"situation\":\"CANCELLED\"}\n"
          }
        ]
      }
    },
    "/v1/events:polling": {
      "get": {
        "tags": [
          "events"
        ],
        "operationId": "pollEvents",
        "summary": "Consultar eventos",
        "description": "Os eventos pendentes da sua credencial, em ordem de `createdAt`, do mais antigo para o\nmais novo. Cada item é um envelope com `eventId`, `eventType`, `orderId`, `orderURL` e\n`createdAt`; o envelope **nunca traz o pedido**. Para `CREATED` e `MODIFIED`, faça `GET`\nem `orderURL` e leia o pedido de agora.\n\nA resposta é `200` com um array, vazio quando não há nada. A API nunca responde `204`.\n\nConsultar não confirma. Um evento devolvido aqui continua pendente até você chamar\n`POST /v1/events/acknowledgment` com o `eventId`; enquanto isso, ele volta em toda\nconsulta. É a garantia at-least-once: se o seu processo cair entre a consulta e a\ngravação, nada se perde. Por isso o mesmo `eventId` pode chegar mais de uma vez: trate-o\ncomo chave única e grave-o na mesma transação em que aplica o efeito.\n\nO estado \"o que ainda falta\" vive na API, por credencial. Não há cursor, `since` nem\nfiltro por tipo; duas credenciais na mesma loja têm feeds independentes; uma credencial\ncriada hoje não recebe os eventos de ontem; uma pausada acumula e entrega quando retomada.\nEventos confirmados são apagados após 30 dias; não confirmados, nunca.\n\nConsulte a cada 5 a 10 segundos em operação normal. Se a resposta vier cheia (`limit`\nitens), consulte de novo logo depois de confirmar, sem esperar o intervalo. Em `429`,\nrespeite o `Retry-After`. Os campos opcionais do envelope (`sourceAppId`, `metadata`,\n`delivery`) só aparecem quando têm valor. O caminho antigo `/v1/events/:polling`, com\nbarra, continua respondendo por compatibilidade e não deve ser usado em integração nova.\n",
        "security": [
          {
            "oauth2": [
              "orders:read"
            ]
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PollingLimit"
          }
        ],
        "responses": {
          "200": {
            "description": "Eventos pendentes. Array vazio quando não há nada.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/EventEnvelope"
                  }
                },
                "examples": {
                  "pending": {
                    "summary": "Um pedido novo e um cancelamento",
                    "value": [
                      {
                        "eventId": "c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f",
                        "eventType": "CREATED",
                        "orderId": "9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e",
                        "orderURL": "https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e",
                        "createdAt": "2026-09-19T14:32:10Z"
                      },
                      {
                        "eventId": "e2c0f3a4-5d6b-4e7c-9f8a-0b1c2d3e4f5a",
                        "eventType": "CANCELLED",
                        "orderId": "7c4d5e6f-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
                        "orderURL": "https://api.meupedido.io/open-delivery/v1/orders/7c4d5e6f-8a9b-4c0d-9e1f-2a3b4c5d6e7f",
                        "createdAt": "2026-09-19T14:33:40Z",
                        "metadata": {
                          "reason": "Cliente desistiu do pedido.",
                          "code": "CONSUMER_CANCELLATION_REQUESTED"
                        }
                      }
                    ]
                  },
                  "empty": {
                    "summary": "Nada pendente",
                    "value": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "`limit` não é um inteiro, como em `?limit=abc` ou num valor fora de int32. Formato\nValidationProblemDetails do ASP.NET, com `Content-Type: application/problem+json`, e\n`errors` aponta o parâmetro. Estar fora da faixa não é erro: 0 ou menos vira 1, e acima\nde 200 vira 200.\n",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationProblemDetails"
                },
                "example": {
                  "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
                  "title": "One or more validation errors occurred.",
                  "status": 400,
                  "errors": {
                    "limit": [
                      "The value 'abc' is not valid."
                    ]
                  },
                  "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersRead"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl \"https://api.meupedido.io/open-delivery/v1/events:polling?limit=100\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\"\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const poll = () =>\n  fetch('https://api.meupedido.io/open-delivery/v1/events:polling?limit=100', {\n    headers: { Authorization: `Bearer ${accessToken}` },\n  });\n\nlet response = await poll();\n\n// Em 429 o corpo é o erro de limite, e não a lista: espere o Retry-After e refaça a\n// requisição antes de ler o JSON.\nif (response.status === 429) {\n  const wait = Number(response.headers.get('retry-after') ?? 60) * 1000;\n  await new Promise((resolve) => setTimeout(resolve, wait));\n  response = await poll();\n}\n\nconst events = await response.json(); // sempre um array, vazio quando não há nada\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.get(\n    \"https://api.meupedido.io/open-delivery/v1/events:polling\",\n    params={\"limit\": 100},\n    headers={\"Authorization\": f\"Bearer {access_token}\"},\n    timeout=15,\n)\nresponse.raise_for_status()\nevents = response.json()  # sempre uma lista, vazia quando não há nada\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "using var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nvar events = await http.GetFromJsonAsync<List<JsonElement>>(\"v1/events:polling?limit=100\");\n// sempre uma lista, vazia quando não há nada\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$ch = curl_init('https://api.meupedido.io/open-delivery/v1/events:polling?limit=100');\ncurl_setopt_array($ch, [\n    CURLOPT_HTTPHEADER => [\"Authorization: Bearer {$accessToken}\"],\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$events = json_decode(curl_exec($ch), true); // sempre um array, vazio quando não há nada\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "HttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/events:polling?limit=100\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .GET()\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// response.body() é um array JSON, vazio quando não há nada.\n"
          }
        ]
      }
    },
    "/v1/events/acknowledgment": {
      "post": {
        "tags": [
          "events"
        ],
        "operationId": "acknowledgeEvents",
        "summary": "Confirmar eventos",
        "description": "Confirma o recebimento dos eventos e os tira do feed da sua credencial. Chame depois de\nprocessar e gravar; nunca antes. É a confirmação, e só ela, que move o feed: consultar não\nconfirma, e receber por webhook também não.\n\nO corpo é um array de objetos com o `id` de cada evento (o `eventId` do envelope). Envie\na lista inteira de um ciclo num POST só, e não um POST por evento. A resposta é `202` com\n`acknowledged`, a quantidade que saiu do feed. Ids que não pertencem à sua credencial, ids\nrepetidos, ids já confirmados e valores que não são GUID são ignorados em silêncio: a\nresposta continua `202`, apenas com a contagem menor. Isso é de propósito, para a operação\nnão virar um oráculo de quais eventos existem para outro consumidor. Um array vazio, ou\nnenhum corpo, responde `202` com `acknowledged: 0`.\n\nO padrão 1.4.0 pede também `orderId` e `eventType` em cada item; esta API aceita só o\n`id` e ignora os demais campos, então um cliente aderente que envie os três funciona.\n",
        "security": [
          {
            "oauth2": [
              "orders:read"
            ]
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Os eventos processados, um objeto por `eventId`.",
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AcknowledgmentItem"
                }
              },
              "example": [
                {
                  "id": "c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f"
                },
                {
                  "id": "e2c0f3a4-5d6b-4e7c-9f8a-0b1c2d3e4f5a"
                }
              ]
            }
          }
        },
        "responses": {
          "202": {
            "description": "Confirmação registrada. `acknowledged` é quantos eventos saíram do feed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcknowledgmentResult"
                },
                "example": {
                  "acknowledged": 2
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/InvalidBody"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/ForbiddenOrdersRead"
          },
          "415": {
            "$ref": "#/components/responses/UnsupportedMediaType"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "curl -X POST \"https://api.meupedido.io/open-delivery/v1/events/acknowledgment\" \\\n  -H \"Authorization: Bearer $ACCESS_TOKEN\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '[{ \"id\": \"c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f\" }, { \"id\": \"e2c0f3a4-5d6b-4e7c-9f8a-0b1c2d3e4f5a\" }]'\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "const response = await fetch('https://api.meupedido.io/open-delivery/v1/events/acknowledgment', {\n  method: 'POST',\n  headers: {\n    Authorization: `Bearer ${accessToken}`,\n    'Content-Type': 'application/json',\n  },\n  body: JSON.stringify(events.map((event) => ({ id: event.eventId }))),\n});\n\nconst { acknowledged } = await response.json();\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "import requests\n\nresponse = requests.post(\n    \"https://api.meupedido.io/open-delivery/v1/events/acknowledgment\",\n    headers={\"Authorization\": f\"Bearer {access_token}\"},\n    json=[{\"id\": event[\"eventId\"]} for event in events],\n    timeout=15,\n)\nacknowledged = response.json()[\"acknowledged\"]\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "using var http = new HttpClient { BaseAddress = new Uri(\"https://api.meupedido.io/open-delivery/\") };\nhttp.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(\"Bearer\", accessToken);\n\nvar body = events.Select(e => new { id = e.GetProperty(\"eventId\").GetString() });\n\nusing var response = await http.PostAsJsonAsync(\"v1/events/acknowledgment\", body);\nvar result = await response.Content.ReadFromJsonAsync<JsonElement>();\nvar acknowledged = result.GetProperty(\"acknowledged\").GetInt32();\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n$body = array_map(fn ($event) => ['id' => $event['eventId']], $events);\n\n$ch = curl_init('https://api.meupedido.io/open-delivery/v1/events/acknowledgment');\ncurl_setopt_array($ch, [\n    CURLOPT_POST => true,\n    CURLOPT_HTTPHEADER => [\n        \"Authorization: Bearer {$accessToken}\",\n        'Content-Type: application/json',\n    ],\n    CURLOPT_POSTFIELDS => json_encode($body),\n    CURLOPT_RETURNTRANSFER => true,\n]);\n\n$result = json_decode(curl_exec($ch), true); // ['acknowledged' => 2]\ncurl_close($ch);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "String body = \"[{\\\"id\\\":\\\"c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f\\\"},{\\\"id\\\":\\\"e2c0f3a4-5d6b-4e7c-9f8a-0b1c2d3e4f5a\\\"}]\";\n\nHttpRequest request = HttpRequest.newBuilder()\n    .uri(URI.create(\"https://api.meupedido.io/open-delivery/v1/events/acknowledgment\"))\n    .header(\"Authorization\", \"Bearer \" + accessToken)\n    .header(\"Content-Type\", \"application/json\")\n    .POST(HttpRequest.BodyPublishers.ofString(body))\n    .build();\n\nHttpResponse<String> response = HttpClient.newHttpClient()\n    .send(request, HttpResponse.BodyHandlers.ofString());\n// 202 com {\"acknowledged\":2}\n"
          }
        ]
      }
    }
  },
  "webhooks": {
    "orderEvent": {
      "post": {
        "tags": [
          "webhooks"
        ],
        "operationId": "orderEvent",
        "summary": "Evento de pedido",
        "x-meupedido-extension": true,
        "description": "Extensão MeuPedido: não existe no padrão 1.4.0 (não é o `/v1/newEvent`).\n\nCom o modo de entrega `WEBHOOK` ou `BOTH`, configurado pelo lojista ao criar a credencial,\no MeuPedido faz um `POST` na sua URL a cada evento, em vez de esperar a sua consulta. O\ncorpo é **o mesmo envelope** que `GET /v1/events:polling` devolve, byte a byte: um único\ndesserializador atende os dois modos. Um envio por evento; o corpo nunca traz o pedido,\nbusque em `orderURL`.\n\n**Contrato da URL.** Obrigatoriamente HTTPS. O segredo do webhook (43 caracteres, base64url)\né gerado pelo MeuPedido e mostrado uma única vez ao salvar a URL; salvar de novo rotaciona\no segredo e o anterior deixa de valer.\n\n**Assinatura.** `X-MeuPedido-Signature` é `HMAC-SHA256(secret, \"{timestamp}.{body}\")` em\nhexadecimal minúsculo, em que `timestamp` é o valor de `X-MeuPedido-Timestamp` (segundos\nUnix) e `body` é o corpo bruto, exatamente como chegou. Verifique antes de qualquer\nprocessamento: rejeite se o timestamp estiver a mais de 5 minutos do seu relógio e compare\na assinatura em tempo constante. Não desserialize e serialize de novo antes de calcular.\n\n**Resposta.** Qualquer `2xx` em até 10 segundos. O corpo da resposta é ignorado. Faça o\nmínimo dentro desse prazo (verificar, enfileirar, responder) e processe depois. Um `2xx`\ntardio conta como timeout.\n\n**Entregue não é confirmado.** O `2xx` diz que a requisição chegou; ele não tira o evento\ndo feed. É obrigatório chamar `POST /v1/events/acknowledgment` com o `eventId` depois de\nprocessar, mesmo recebendo por webhook. Sem isso o evento continua no polling.\n\n**Retentativas.** Resposta fora de `2xx`, conexão recusada ou timeout contam como falha, e\no MeuPedido tenta de novo após 5, 10 e 20 segundos (4 tentativas em cerca de 35 s).\nEsgotadas, o evento é marcado como `WEBHOOK_FAILED` e **continua disponível no polling**.\nApós 20 falhas consecutivas a URL é desativada e o lojista a reativa no painel (\"Limpar\nfila\"); os eventos continuam sendo gerados e ficam no polling. Retentativa e modo `BOTH`\nfazem o mesmo `eventId` chegar mais de uma vez: deduplique por ele.\n",
        "security": [],
        "parameters": [
          {
            "name": "X-MeuPedido-Signature",
            "in": "header",
            "required": true,
            "description": "HMAC-SHA256 sobre `\"{timestamp}.{body}\"` com o segredo do webhook, em hexadecimal minúsculo (64 caracteres). Compare em tempo constante.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9a-f]{64}$"
            },
            "example": "5f1d6a9c2b3e4f7a8d9c0b1e2f3a4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e"
          },
          {
            "name": "X-MeuPedido-Timestamp",
            "in": "header",
            "required": true,
            "description": "Instante do envio em segundos Unix. Entra no cálculo da assinatura; rejeite se estiver a mais de 5 minutos do seu relógio.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            },
            "example": "1789828331"
          },
          {
            "name": "X-MeuPedido-Event-Id",
            "in": "header",
            "required": true,
            "description": "O mesmo `eventId` do corpo, para roteamento e deduplicação sem desserializar.",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f"
          },
          {
            "name": "X-MeuPedido-Event-Type",
            "in": "header",
            "required": true,
            "description": "O mesmo `eventType` do corpo.",
            "schema": {
              "type": "string"
            },
            "example": "CREATED"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "O envelope do evento, idêntico ao item do polling.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookEvent"
              },
              "example": {
                "eventId": "c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f",
                "eventType": "CREATED",
                "orderId": "9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e",
                "orderURL": "https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e",
                "createdAt": "2026-09-19T14:32:10Z"
              }
            }
          }
        },
        "responses": {
          "2XX": {
            "description": "Entregue. Qualquer status 2xx em até 10 segundos conta como entrega; o corpo é ignorado. Não confirma o evento."
          },
          "4XX": {
            "description": "Falha de entrega. Conta como tentativa falha e agenda a retentativa (5, 10 e 20 s). Vinte falhas consecutivas desativam a URL."
          },
          "5XX": {
            "description": "Falha de entrega. Mesmo tratamento de um 4xx."
          }
        },
        "x-codeSamples": [
          {
            "lang": "shell",
            "label": "cURL",
            "source": "# O que o MeuPedido envia para a sua URL (o valor da assinatura depende do seu segredo).\ncurl -X POST \"https://sua-url.example/webhooks/meupedido\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"X-MeuPedido-Signature: 5f1d6a9c2b3e4f7a8d9c0b1e2f3a4d5c6b7a8f9e0d1c2b3a4f5e6d7c8b9a0f1e\" \\\n  -H \"X-MeuPedido-Timestamp: 1789828331\" \\\n  -H \"X-MeuPedido-Event-Id: c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f\" \\\n  -H \"X-MeuPedido-Event-Type: CREATED\" \\\n  -d '{\"eventId\":\"c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f\",\"eventType\":\"CREATED\",\"orderId\":\"9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\",\"orderURL\":\"https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e\",\"createdAt\":\"2026-09-19T14:32:10Z\"}'\n"
          },
          {
            "lang": "javascript",
            "label": "JavaScript (fetch)",
            "source": "// Receptor em Node 20+: verifica a assinatura sobre o corpo bruto e responde antes de processar.\nimport { createHmac, timingSafeEqual } from 'node:crypto';\n\nexport function verifySignature(secret, timestamp, body, signature, now = Math.floor(Date.now() / 1000)) {\n  const sent = Number(timestamp);\n  if (!Number.isInteger(sent) || Math.abs(now - sent) > 300) return false;\n\n  const expected = Buffer.from(createHmac('sha256', secret).update(`${timestamp}.${body}`).digest('hex'));\n  const received = Buffer.from(String(signature ?? '').toLowerCase());\n  return expected.length === received.length && timingSafeEqual(expected, received);\n}\n\n// app.post('/webhooks/meupedido', express.raw({ type: 'application/json' }), (req, res) => {\n//   const body = req.body.toString('utf8');\n//   if (!verifySignature(process.env.MEUPEDIDO_WEBHOOK_SECRET, req.get('X-MeuPedido-Timestamp'), body, req.get('X-MeuPedido-Signature'))) {\n//     return res.status(401).end();\n//   }\n//   queue.push(JSON.parse(body));\n//   res.status(204).end();\n// });\n"
          },
          {
            "lang": "python",
            "label": "Python (requests)",
            "source": "# Receptor: verifica a assinatura sobre o corpo bruto e responde antes de processar.\nimport hashlib\nimport hmac\nimport time\n\n\ndef verify_signature(secret: str, timestamp: str, body: str, signature: str) -> bool:\n    try:\n        sent = int(timestamp)\n    except (TypeError, ValueError):\n        return False\n    if abs(int(time.time()) - sent) > 300:\n        return False\n\n    expected = hmac.new(secret.encode(\"utf-8\"), f\"{timestamp}.{body}\".encode(\"utf-8\"), hashlib.sha256).hexdigest()\n    return hmac.compare_digest(expected, (signature or \"\").lower())\n\n\n# @app.post(\"/webhooks/meupedido\")\n# def meupedido_webhook():\n#     body = request.get_data(as_text=True)\n#     if not verify_signature(os.environ[\"MEUPEDIDO_WEBHOOK_SECRET\"], request.headers.get(\"X-MeuPedido-Timestamp\", \"\"), body, request.headers.get(\"X-MeuPedido-Signature\", \"\")):\n#         abort(401)\n#     queue.push(request.get_json())\n#     return \"\", 204\n"
          },
          {
            "lang": "csharp",
            "label": "C# (HttpClient)",
            "source": "// Receptor: verifica a assinatura sobre o corpo bruto e responde antes de processar.\nusing System.Security.Cryptography;\nusing System.Text;\n\npublic static class WebhookSignature\n{\n    public static bool Verify(string secret, string timestamp, string body, string signature)\n    {\n        var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds();\n        if (!long.TryParse(timestamp, out var sent) || Math.Abs(now - sent) > 300)\n            return false;\n\n        var hash = HMACSHA256.HashData(Encoding.UTF8.GetBytes(secret), Encoding.UTF8.GetBytes($\"{timestamp}.{body}\"));\n        var expected = Encoding.UTF8.GetBytes(Convert.ToHexString(hash).ToLowerInvariant());\n        var received = Encoding.UTF8.GetBytes((signature ?? string.Empty).ToLowerInvariant());\n\n        return CryptographicOperations.FixedTimeEquals(expected, received);\n    }\n}\n"
          },
          {
            "lang": "php",
            "label": "PHP (curl)",
            "source": "<?php\n// Receptor: verifica a assinatura sobre o corpo bruto e responde antes de processar.\nfunction verifySignature(string $secret, string $timestamp, string $body, string $signature): bool\n{\n    if (!ctype_digit($timestamp) || abs(time() - (int) $timestamp) > 300) {\n        return false;\n    }\n\n    $expected = hash_hmac('sha256', \"{$timestamp}.{$body}\", $secret);\n    return hash_equals($expected, strtolower($signature));\n}\n\n$body = file_get_contents('php://input');\n$ok = verifySignature(\n    getenv('MEUPEDIDO_WEBHOOK_SECRET'),\n    $_SERVER['HTTP_X_MEUPEDIDO_TIMESTAMP'] ?? '',\n    $body,\n    $_SERVER['HTTP_X_MEUPEDIDO_SIGNATURE'] ?? '',\n);\n\nhttp_response_code($ok ? 204 : 401);\n"
          },
          {
            "lang": "java",
            "label": "Java (HttpClient)",
            "source": "// Receptor: verifica a assinatura sobre o corpo bruto e responde antes de processar.\nimport java.nio.charset.StandardCharsets;\nimport java.security.MessageDigest;\nimport java.util.HexFormat;\nimport javax.crypto.Mac;\nimport javax.crypto.spec.SecretKeySpec;\n\npublic final class WebhookSignature {\n    public static boolean verify(String secret, String timestamp, String body, String signature) throws Exception {\n        long sent;\n        try {\n            sent = Long.parseLong(timestamp);\n        } catch (NumberFormatException e) {\n            return false;\n        }\n        if (Math.abs(System.currentTimeMillis() / 1000 - sent) > 300) return false;\n\n        Mac mac = Mac.getInstance(\"HmacSHA256\");\n        mac.init(new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), \"HmacSHA256\"));\n        byte[] hash = mac.doFinal((timestamp + \".\" + body).getBytes(StandardCharsets.UTF_8));\n\n        byte[] expected = HexFormat.of().formatHex(hash).getBytes(StandardCharsets.UTF_8);\n        byte[] received = signature.toLowerCase().getBytes(StandardCharsets.UTF_8);\n        return MessageDigest.isEqual(expected, received);\n    }\n}\n"
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OAuth 2.0 `client_credentials`. Troque `client_id` e `client_secret` por um token em\n`POST /oauth/token` e envie `Authorization: Bearer <access_token>`. O token vale 3600\nsegundos, sem refresh token. Os escopos são definidos pelo lojista ao criar a credencial e\nnão podem ser alterados depois; o token carrega exatamente os escopos da credencial.\n",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://api.meupedido.io/open-delivery/oauth/token",
            "scopes": {
              "orders:read": "Ler pedidos, consultar eventos e confirmá-los: GET /v1/orders/{orderId}, GET /v1/events:polling, POST /v1/events/acknowledgment.",
              "orders:write": "Mover o pedido pelo ciclo de vida: todas as ações em POST /v1/orders/{orderId}/....",
              "merchant:read": "Ler os dados da loja: GET /v1/merchant/{merchantId}.",
              "catalog:read": "Ler o cardápio: GET /v1/merchant/{merchantId}/menus."
            }
          }
        }
      }
    },
    "parameters": {
      "OrderId": {
        "name": "orderId",
        "in": "path",
        "required": true,
        "description": "Identificador do pedido (GUID). É o `orderId` dos eventos e o `id` do pedido. Um valor que não é GUID responde `404` no formato ProblemDetails.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e"
      },
      "MerchantId": {
        "name": "merchantId",
        "in": "path",
        "required": true,
        "description": "Identificador da loja (GUID). Precisa ser o da credencial, o mesmo da claim `merchant_id` do token e de `merchant.id` de todo pedido. Qualquer outro valor responde `404`.",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "x-meupedido-extension": true,
        "description": "Extensão MeuPedido: não existe no padrão 1.4.0.\n\nChave de idempotência, qualquer string de **até 128 caracteres** escolhida por você, única\npor credencial (um UUID por tentativa lógica, ou um valor derivado como\n`confirm-{orderId}`). Mesma chave com a mesma requisição (mesma rota, mesma ação, mesmo\ncorpo) devolve a resposta gravada na primeira execução, com o mesmo status e o mesmo corpo,\nsem executar de novo, por 24 horas. Mesma chave com requisição diferente responde\n`409 idempotency_key_reuse`. Respostas `202`, `404` e `422` são gravadas; `5xx` não, para\nque a repetição execute de verdade. Em timeout, repita com a mesma chave.\n\nAcima de 128 caracteres a resposta é `400 invalid_idempotency_key` e **nada é executado**:\n128 é o tamanho que a API grava, e aceitar uma chave maior seria aplicar o comando sem a\nproteção que você pediu.\n\nEste parâmetro não traz `example` de propósito: a chave muda a cada intenção nova, e um\nvalor fixo, repetido do playground ou do cURL copiado numa segunda ação, responderia `409`.\n",
        "schema": {
          "type": "string",
          "maxLength": 128
        }
      },
      "PollingLimit": {
        "name": "limit",
        "in": "query",
        "required": false,
        "x-meupedido-extension": true,
        "description": "Extensão MeuPedido: não existe no padrão 1.4.0.\n\nQuantidade máxima de eventos na resposta. Padrão 100, máximo 200: valores maiores são\nreduzidos para 200 e valores menores que 1, elevados para 1. Se a resposta vier cheia,\nconsulte de novo logo depois de confirmar.\n",
        "schema": {
          "type": "integer",
          "format": "int32",
          "minimum": 1,
          "maximum": 200,
          "default": 100
        },
        "example": 100
      }
    },
    "headers": {
      "RetryAfter": {
        "description": "Segundos a esperar antes de tentar de novo. Respeite o valor; tentar antes não faz o limite abrir.",
        "schema": {
          "type": "integer",
          "format": "int32"
        },
        "example": 60
      },
      "WWWAuthenticate": {
        "description": "Desafio Bearer (RFC 6750). Traz `error=\"invalid_token\"` e `error_description` quando o token foi recusado; só `Bearer` quando não havia token.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer error=\"invalid_token\", error_description=\"A credencial foi pausada ou revogada pelo lojista.\""
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Sem token, token expirado ou inválido: corpo vazio, com `WWW-Authenticate`. Peça um token\nnovo uma vez e repita a chamada. Credencial pausada ou revogada pelo lojista: corpo com\n`invalid_token`, imediato, mesmo para tokens dentro da validade; o endpoint de token\ntambém vai recusá-la. Pare e avise o operador.\n",
        "headers": {
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWWAuthenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "invalid_token",
              "message": "A credencial foi pausada ou revogada pelo lojista."
            }
          }
        }
      },
      "ForbiddenOrdersRead": {
        "description": "O token é válido, mas a credencial não tem o escopo `orders:read`. Não peça outro token; o lojista precisa criar uma credencial com o escopo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "insufficient_scope",
              "message": "A credencial não tem o escopo 'orders:read'."
            }
          }
        }
      },
      "ForbiddenOrdersWrite": {
        "description": "O token é válido, mas a credencial não tem o escopo `orders:write`. Não peça outro token; o lojista precisa criar uma credencial com o escopo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "insufficient_scope",
              "message": "A credencial não tem o escopo 'orders:write'."
            }
          }
        }
      },
      "ForbiddenMerchantRead": {
        "description": "O token é válido, mas a credencial não tem o escopo `merchant:read`. Não peça outro token; o lojista precisa criar uma credencial com o escopo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "insufficient_scope",
              "message": "A credencial não tem o escopo 'merchant:read'."
            }
          }
        }
      },
      "ForbiddenCatalogRead": {
        "description": "O token é válido, mas a credencial não tem o escopo `catalog:read`. Não peça outro token; o lojista precisa criar uma credencial com o escopo.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "insufficient_scope",
              "message": "A credencial não tem o escopo 'catalog:read'."
            }
          }
        }
      },
      "OrderNotFound": {
        "description": "`order_not_found` (`Content-Type: application/json`): o pedido não existe ou não pertence\nà loja da credencial, sem distinção. Um `orderId` que não é GUID responde no formato\nProblemDetails do ASP.NET, com `Content-Type: application/problem+json`. Nos dois casos,\nnão repita: confira o id.\n",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "order_not_found",
              "message": "Pedido não localizado."
            }
          },
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
              "title": "Not Found",
              "status": 404,
              "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
            }
          }
        }
      },
      "MerchantNotFound": {
        "description": "O `merchantId` não é o da credencial, ou não é GUID. Formato ProblemDetails do ASP.NET, com `Content-Type: application/problem+json`. Use o id da claim `merchant_id` do token.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "https://tools.ietf.org/html/rfc9110#section-15.5.5",
              "title": "Not Found",
              "status": 404,
              "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
            }
          }
        }
      },
      "IdempotencyKeyReuse": {
        "description": "A mesma `Idempotency-Key` já foi usada nesta credencial com outra rota, outra ação ou outro corpo. Nada foi executado. Gere uma chave nova para a nova intenção.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "idempotency_key_reuse",
              "message": "Esta chave de idempotência já foi usada com outro conteúdo."
            }
          }
        }
      },
      "InvalidIdempotencyKey": {
        "description": "A `Idempotency-Key` passou de 128 caracteres, o tamanho que a API grava. Nada foi executado e o pedido continua como estava. Encurte a chave (um UUID basta) e repita; a mesma chave longa responde sempre isto.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ApiError"
            },
            "example": {
              "error": "invalid_idempotency_key",
              "message": "A chave de idempotência tem no máximo 128 caracteres."
            }
          }
        }
      },
      "InvalidBody": {
        "description": "O corpo não é JSON válido para esta operação. Formato ValidationProblemDetails do ASP.NET, com `Content-Type: application/problem+json`; `errors` aponta o campo. Corrija a requisição.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationProblemDetails"
            },
            "example": {
              "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
              "title": "One or more validation errors occurred.",
              "status": 400,
              "errors": {
                "$": [
                  "'<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0."
                ]
              },
              "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
            }
          }
        }
      },
      "UnsupportedMediaType": {
        "description": "`Content-Type` diferente de `application/json` com corpo presente. Formato ProblemDetails do ASP.NET, com `Content-Type: application/problem+json`.",
        "content": {
          "application/problem+json": {
            "schema": {
              "$ref": "#/components/schemas/ProblemDetails"
            },
            "example": {
              "type": "https://tools.ietf.org/html/rfc9110#section-15.5.16",
              "title": "Unsupported Media Type",
              "status": 415,
              "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Mais de 600 requisições por minuto nesta credencial. Espere o `Retry-After` antes de tentar de novo; tentar antes não faz o limite abrir.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/RateLimitError"
            },
            "example": {
              "error": "rate_limit_exceeded",
              "message": "Limite de requisições excedido."
            }
          }
        }
      },
      "InternalError": {
        "description": "Falha interna. A mensagem não descreve a causa de propósito; informe o `traceId` ao suporte. Repita com backoff exponencial.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/InternalError"
            },
            "example": {
              "error": "internal_error",
              "message": "Erro interno. Informe o traceId ao suporte.",
              "traceId": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
            }
          }
        }
      }
    },
    "examples": {
      "OrderDelivery": {
        "summary": "Pedido de entrega imediato, pago no cartão na porta",
        "description": "Saída real do serializador para um pedido da loja de teste. Campos que a API ainda não preenche chegam como `null`.",
        "value": {
          "id": "9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e",
          "displayId": "1042",
          "type": "DELIVERY",
          "orderTiming": "INSTANT",
          "createdAt": "2026-09-19T14:32:10Z",
          "preparationStartDateTime": "2026-09-19T14:32:10Z",
          "merchant": {
            "id": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d",
            "name": "Loja de teste Acme"
          },
          "customer": {
            "id": "a4b5c6d7-e8f9-4a0b-8c1d-2e3f4a5b6c7d",
            "name": "Ana Souza",
            "phone": {
              "number": "11999990000",
              "extension": null
            },
            "documentNumber": null,
            "email": null,
            "ordersCountOnMerchant": null
          },
          "items": [
            {
              "id": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b",
              "index": null,
              "name": "Pizza Margherita Grande",
              "externalCode": "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d",
              "unit": null,
              "ean": null,
              "quantity": 1,
              "specialInstructions": "Sem manjericão",
              "unitPrice": {
                "value": 49.9,
                "currency": "BRL"
              },
              "optionsPrice": {
                "value": 8,
                "currency": "BRL"
              },
              "totalPrice": {
                "value": 57.9,
                "currency": "BRL"
              },
              "options": [
                {
                  "id": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
                  "index": null,
                  "name": "Borda recheada",
                  "externalCode": "b8a7c6d5-e4f3-4210-9876-543210fedcba",
                  "quantity": 1,
                  "unit": null,
                  "unitPrice": {
                    "value": 8,
                    "currency": "BRL"
                  },
                  "price": {
                    "value": 8,
                    "currency": "BRL"
                  },
                  "groupName": null
                }
              ]
            }
          ],
          "otherFees": [
            {
              "name": "Taxa de entrega",
              "type": "DELIVERY",
              "receivedBy": "MERCHANT",
              "price": {
                "value": 7,
                "currency": "BRL"
              }
            }
          ],
          "discounts": null,
          "total": {
            "items": {
              "value": 57.9,
              "currency": "BRL"
            },
            "otherFees": {
              "value": 7,
              "currency": "BRL"
            },
            "discount": {
              "value": 0,
              "currency": "BRL"
            },
            "orderAmount": {
              "value": 64.9,
              "currency": "BRL"
            }
          },
          "payments": {
            "prepaid": 0,
            "pending": 64.9,
            "methods": [
              {
                "value": 64.9,
                "currency": "BRL",
                "method": "CREDIT",
                "methodInfo": "Cartão de crédito",
                "type": "OFFLINE",
                "changeFor": null,
                "brand": null,
                "transaction": null
              }
            ]
          },
          "delivery": {
            "deliveryDateTime": null,
            "estimatedDeliveryDateTime": null,
            "deliveredBy": "MERCHANT",
            "deliveryAddress": {
              "country": "BR",
              "state": "SP",
              "city": "São Paulo",
              "district": "Pinheiros",
              "street": "Rua dos Pinheiros",
              "number": "1000",
              "postalCode": "05422001",
              "complement": "Apto 42",
              "reference": null,
              "formattedAddress": null,
              "coordinates": {
                "latitude": -23.5656,
                "longitude": -46.6898
              }
            }
          },
          "takeout": null,
          "schedule": null,
          "extraInfo": "Interfone 42 | origem=SITE",
          "test": true
        }
      }
    },
    "schemas": {
      "TokenRequest": {
        "type": "object",
        "title": "TokenRequest",
        "description": "Pedido de token (RFC 6749, seção 4.4). Em JSON os campos também são aceitos em camelCase (`grantType`, `clientId`, `clientSecret`).",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "description": "Único fluxo suportado.",
            "enum": [
              "client_credentials"
            ]
          },
          "client_id": {
            "type": "string",
            "description": "Identificador da credencial, exibido no painel do lojista. Formato `mp_` seguido de 24 caracteres hexadecimais.",
            "maxLength": 128,
            "example": "mp_7f3a9c1e5b2d4a6f8e0c1b3d"
          },
          "client_secret": {
            "type": "string",
            "format": "password",
            "description": "Segredo mostrado uma única vez ao criar a credencial (43 caracteres, base64url). Nunca o coloque em código, log ou URL.",
            "maxLength": 128,
            "example": "cole-aqui-o-segredo-mostrado-no-painel"
          }
        }
      },
      "TokenResponse": {
        "type": "object",
        "title": "TokenResponse",
        "description": "Token emitido. Cada campo vem em snake_case (RFC 6749) e em camelCase (texto do padrão Open Delivery), com valores idênticos; leia o que a sua biblioteca esperar.",
        "required": [
          "access_token",
          "accessToken",
          "token_type",
          "tokenType",
          "expires_in",
          "expiresIn",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "O JWT a enviar em `Authorization: Bearer`. Trate como opaco.",
            "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."
          },
          "accessToken": {
            "type": "string",
            "description": "O mesmo valor de `access_token`, em camelCase.",
            "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ..."
          },
          "token_type": {
            "type": "string",
            "description": "Sempre `bearer`, em minúsculas, como o enum do padrão define. O cabeçalho continua sendo `Authorization: Bearer`.",
            "enum": [
              "bearer"
            ]
          },
          "tokenType": {
            "type": "string",
            "description": "O mesmo valor de `token_type`, em camelCase.",
            "enum": [
              "bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "format": "int32",
            "description": "Validade em segundos a partir da emissão. Sempre 3600.",
            "example": 3600
          },
          "expiresIn": {
            "type": "integer",
            "format": "int32",
            "description": "O mesmo valor de `expires_in`, em camelCase.",
            "example": 3600
          },
          "scope": {
            "type": "string",
            "description": "Os escopos da credencial, separados por espaço.",
            "example": "orders:read orders:write merchant:read catalog:read"
          }
        }
      },
      "TokenError": {
        "type": "object",
        "title": "TokenError",
        "description": "Erro do endpoint de token, no formato do RFC 6749, seção 5.2. O `401 invalid_client` vem sem `error_description`, de propósito.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Código estável do erro.",
            "enum": [
              "invalid_request",
              "unsupported_grant_type",
              "invalid_client",
              "rate_limit_exceeded"
            ],
            "x-enumDescriptions": {
              "invalid_request": "Falta grant_type, client_id ou client_secret; corpo malformado ou acima de 4 KB; Content-Type não suportado; Authorization Basic ilegível.",
              "unsupported_grant_type": "grant_type diferente de client_credentials.",
              "invalid_client": "Credencial inexistente, segredo errado, pausada ou revogada. Uma resposta só.",
              "rate_limit_exceeded": "Dez falhas de autenticação nesta credencial em um minuto."
            }
          },
          "error_description": {
            "type": "string",
            "description": "Texto em pt-BR para o seu log. Ausente no `401`.",
            "example": "Informe client_id e client_secret."
          }
        }
      },
      "ApiError": {
        "type": "object",
        "title": "ApiError",
        "description": "Erro das rotas autenticadas. `error` é o código estável para o seu código; `message` é o texto em pt-BR para o seu log.",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Código estável do erro.",
            "enum": [
              "invalid_idempotency_key",
              "invalid_cancellation_reason",
              "invalid_token",
              "insufficient_scope",
              "order_not_found",
              "idempotency_key_reuse",
              "invalid_transition"
            ],
            "x-enumDescriptions": {
              "invalid_idempotency_key": "A Idempotency-Key passou de 128 caracteres (400).",
              "invalid_cancellation_reason": "O motivo do cancelamento passou de 500 caracteres (400).",
              "invalid_token": "Credencial pausada ou revogada pelo lojista (401).",
              "insufficient_scope": "O token não tem o escopo exigido pela operação (403).",
              "order_not_found": "O pedido não existe ou não pertence à loja da credencial (404).",
              "idempotency_key_reuse": "A mesma Idempotency-Key foi usada com outra requisição (409).",
              "invalid_transition": "A ação não é permitida a partir da situação atual do pedido (422)."
            }
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Explicação em pt-BR.",
            "example": "Pedido não localizado."
          }
        }
      },
      "RateLimitError": {
        "type": "object",
        "title": "RateLimitError",
        "description": "Limite de requisições excedido. Vem com o cabeçalho `Retry-After`.",
        "required": [
          "error",
          "message"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Sempre `rate_limit_exceeded`.",
            "enum": [
              "rate_limit_exceeded"
            ]
          },
          "message": {
            "type": "string",
            "description": "Explicação em pt-BR.",
            "example": "Limite de requisições excedido."
          }
        }
      },
      "InternalError": {
        "type": "object",
        "title": "InternalError",
        "description": "Falha interna. A mensagem não descreve a causa de propósito; o `traceId` é o que o suporte usa para localizar o evento.",
        "required": [
          "error",
          "message",
          "traceId"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Sempre `internal_error`.",
            "enum": [
              "internal_error"
            ]
          },
          "message": {
            "type": "string",
            "description": "Explicação em pt-BR.",
            "example": "Erro interno. Informe o traceId ao suporte."
          },
          "traceId": {
            "type": "string",
            "description": "Identificador do rastro (W3C Trace Context). Informe ao suporte.",
            "example": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
          }
        }
      },
      "ProblemDetails": {
        "type": "object",
        "title": "ProblemDetails",
        "description": "Erro automático do ASP.NET (RFC 9457) para rota inexistente, `merchantId` de outra loja ou id que não é GUID, e para `Content-Type` não suportado. Chega com `Content-Type: application/problem+json` e sem o campo `error`.",
        "required": [
          "title",
          "status"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Referência do status HTTP.",
            "example": "https://tools.ietf.org/html/rfc9110#section-15.5.5"
          },
          "title": {
            "type": "string",
            "description": "Descrição curta do status, em inglês.",
            "example": "Not Found"
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "description": "O status HTTP, repetido no corpo.",
            "example": 404
          },
          "detail": {
            "type": "string",
            "description": "Detalhe, quando existe."
          },
          "instance": {
            "type": "string",
            "description": "A rota, quando informada."
          },
          "traceId": {
            "type": "string",
            "description": "Identificador do rastro. Informe ao suporte.",
            "example": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
          }
        }
      },
      "ValidationProblemDetails": {
        "type": "object",
        "title": "ValidationProblemDetails",
        "description": "Requisição inválida em rota de negócio, por corpo JSON malformado ou por parâmetro de consulta com tipo errado. ProblemDetails com o mapa `errors`, em que a chave `$` aponta o corpo inteiro e o nome do parâmetro aponta a consulta.",
        "required": [
          "title",
          "status",
          "errors"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "Referência do status HTTP.",
            "example": "https://tools.ietf.org/html/rfc9110#section-15.5.1"
          },
          "title": {
            "type": "string",
            "description": "Descrição curta, em inglês.",
            "example": "One or more validation errors occurred."
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "description": "Sempre 400.",
            "example": 400
          },
          "errors": {
            "type": "object",
            "description": "Mensagens por campo. A chave `$` refere-se ao corpo inteiro.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          "traceId": {
            "type": "string",
            "description": "Identificador do rastro. Informe ao suporte.",
            "example": "00-8a3f1c2e9b4d5f6a7b8c9d0e1f2a3b4c-5d6e7f8a9b0c1d2e-00"
          }
        }
      },
      "TransitionResult": {
        "type": "object",
        "title": "TransitionResult",
        "description": "Resultado de uma ação de ciclo de vida. Sempre `202`; `status` diz se a transição foi aplicada agora ou já estava aplicada.",
        "required": [
          "status",
          "situation"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "`accepted` quando a transição foi aplicada; `already_applied` quando o pedido já estava nessa situação (nada mudou, nenhum evento foi emitido).",
            "enum": [
              "accepted",
              "already_applied"
            ]
          },
          "situation": {
            "type": "string",
            "description": "A situação do pedido depois da ação.",
            "enum": [
              "ACCEPTED",
              "PREPARING",
              "READY",
              "DELIVERY",
              "DONE",
              "CANCELLED"
            ],
            "x-enumDescriptions": {
              "ACCEPTED": "A loja aceitou o pedido (confirm).",
              "PREPARING": "A cozinha começou (startPreparation, extensão MeuPedido).",
              "READY": "Pronto para sair ou para retirada (readyForPickup).",
              "DELIVERY": "Saiu para entrega (dispatch).",
              "DONE": "Entregue ou retirado; encerrado (delivered, pickedUp).",
              "CANCELLED": "Cancelado; final (requestCancellation)."
            }
          }
        }
      },
      "CancellationRequest": {
        "type": "object",
        "title": "CancellationRequest",
        "description": "Corpo opcional de `requestCancellation`.",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Motivo registrado no histórico do pedido e mostrado ao lojista. Vai em `metadata.reason` do evento `CANCELLED`. Padrão \"Cancelamento solicitado pela API pública.\". Acima de 500 caracteres, que é o tamanho gravado no histórico, a resposta é `400 invalid_cancellation_reason` e o pedido não é cancelado.",
            "maxLength": 500,
            "example": "Produto em falta."
          },
          "code": {
            "type": "string",
            "description": "Aceito por compatibilidade com o padrão e ignorado. O `metadata.code` do evento `CANCELLED` deriva de quem cancelou, não deste campo.",
            "example": "OTHER_CANCELLATION_REASON"
          }
        }
      },
      "AcknowledgmentItem": {
        "type": "object",
        "title": "AcknowledgmentItem",
        "description": "Um evento a confirmar. Só o `id` é lido; `orderId` e `eventType` do padrão são aceitos e ignorados.",
        "required": [
          "id"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "O `eventId` do envelope.",
            "example": "c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f"
          }
        }
      },
      "AcknowledgmentResult": {
        "type": "object",
        "title": "AcknowledgmentResult",
        "description": "Resultado da confirmação.",
        "required": [
          "acknowledged"
        ],
        "properties": {
          "acknowledged": {
            "type": "integer",
            "format": "int32",
            "description": "Quantos eventos saíram do feed. Menor que o enviado quando havia ids repetidos, já confirmados, de outra credencial ou que não são GUID.",
            "minimum": 0,
            "example": 2
          }
        }
      },
      "Money": {
        "type": "object",
        "title": "Money",
        "description": "Valor monetário. Copiado do que a loja cobrou, nunca recalculado.",
        "required": [
          "value",
          "currency"
        ],
        "properties": {
          "value": {
            "type": "number",
            "description": "Valor com até quatro casas decimais.",
            "example": 49.9
          },
          "currency": {
            "type": "string",
            "description": "Código ISO 4217. Sempre `BRL`.",
            "pattern": "^[A-Z]{3}$",
            "example": "BRL"
          }
        }
      },
      "Order": {
        "type": "object",
        "title": "Order",
        "description": "O pedido no formato do padrão Open Delivery 1.4.0. Campo sem valor vem como `null`; a chave está sempre presente.",
        "required": [
          "id",
          "displayId",
          "type",
          "orderTiming",
          "createdAt",
          "preparationStartDateTime",
          "merchant",
          "customer",
          "items",
          "otherFees",
          "discounts",
          "total",
          "payments",
          "delivery",
          "takeout",
          "schedule",
          "extraInfo",
          "test"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador do pedido. É o `orderId` dos eventos e das ações. Use como chave.",
            "example": "9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e"
          },
          "displayId": {
            "type": "string",
            "description": "Número curto do pedido, o que aparece no cupom e na tela da loja. Não é único para sempre.",
            "example": "1042"
          },
          "type": {
            "type": "string",
            "description": "`DELIVERY` para entrega no endereço do cliente; `TAKEOUT` para retirada no estabelecimento; `INDOOR` para consumo no local (mesa).",
            "enum": [
              "DELIVERY",
              "TAKEOUT",
              "INDOOR"
            ]
          },
          "orderTiming": {
            "type": "string",
            "description": "`INSTANT` para pedido imediato; `SCHEDULED` para agendado, acompanhado de `schedule`.",
            "enum": [
              "INSTANT",
              "SCHEDULED"
            ]
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Instante de criação, UTC com `Z`.",
            "example": "2026-09-19T14:32:10Z"
          },
          "preparationStartDateTime": {
            "type": "string",
            "format": "date-time",
            "description": "Quando começar a preparar. Igual a `createdAt` em pedido `INSTANT`; igual a `schedule.scheduledDateTimeStart` em `SCHEDULED`.",
            "example": "2026-09-19T14:32:10Z"
          },
          "merchant": {
            "$ref": "#/components/schemas/OrderMerchant"
          },
          "customer": {
            "$ref": "#/components/schemas/OrderCustomer"
          },
          "items": {
            "type": "array",
            "description": "Itens do pedido.",
            "items": {
              "$ref": "#/components/schemas/OrderItem"
            }
          },
          "otherFees": {
            "type": [
              "array",
              "null"
            ],
            "description": "Taxas além dos itens (entrega, acréscimo). `null` quando não há.",
            "items": {
              "$ref": "#/components/schemas/OtherFee"
            }
          },
          "discounts": {
            "type": [
              "array",
              "null"
            ],
            "description": "Descontos aplicados. `null` quando não há.",
            "items": {
              "$ref": "#/components/schemas/Discount"
            }
          },
          "total": {
            "$ref": "#/components/schemas/OrderTotal"
          },
          "payments": {
            "$ref": "#/components/schemas/OrderPayments"
          },
          "delivery": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DeliveryInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Só em pedido `DELIVERY`; `null` nos demais."
          },
          "takeout": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/TakeoutInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Só em pedido `TAKEOUT` e `INDOOR`; `null` em `DELIVERY`."
          },
          "schedule": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/ScheduleInfo"
              },
              {
                "type": "null"
              }
            ],
            "description": "Só em pedido `SCHEDULED`; `null` em `INSTANT`."
          },
          "extraInfo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do pedido e origem, separadas por ` | `: `\"obs | origem=X | canal=Y | numeroCanal=Z\"`. Partes sem valor são omitidas; `null` quando nenhuma existe. Guarde como texto.",
            "example": "Interfone 42 | origem=SITE"
          },
          "test": {
            "type": "boolean",
            "description": "`true` em pedido de loja de teste. Nunca leve um pedido de teste para o seu faturamento.",
            "example": true
          }
        }
      },
      "OrderMerchant": {
        "type": "object",
        "title": "OrderMerchant",
        "description": "A loja dona do pedido.",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id da loja. É o `merchant_id` da sua credencial e o `merchantId` das rotas de loja.",
            "example": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome de exibição da loja.",
            "example": "Loja de teste Acme"
          }
        }
      },
      "OrderCustomer": {
        "type": "object",
        "title": "OrderCustomer",
        "description": "Dados do cliente, como informados no pedido.",
        "required": [
          "id",
          "name",
          "phone",
          "documentNumber",
          "email",
          "ordersCountOnMerchant"
        ],
        "properties": {
          "id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Identificador do cliente no MeuPedido (GUID), quando há cadastro.",
            "example": "a4b5c6d7-e8f9-4a0b-8c1d-2e3f4a5b6c7d"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome informado no pedido.",
            "example": "Ana Souza"
          },
          "phone": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CustomerPhone"
              },
              {
                "type": "null"
              }
            ],
            "description": "Telefone do cliente. `null` quando não informado."
          },
          "documentNumber": {
            "type": [
              "string",
              "null"
            ],
            "description": "CPF ou CNPJ, quando informado para a nota."
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "description": "E-mail, quando informado."
          },
          "ordersCountOnMerchant": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Quantidade de pedidos do cliente nesta loja. A API ainda não preenche; chega como `null`."
          }
        }
      },
      "CustomerPhone": {
        "type": "object",
        "title": "CustomerPhone",
        "description": "Telefone do cliente.",
        "required": [
          "number",
          "extension"
        ],
        "properties": {
          "number": {
            "type": "string",
            "description": "Número como o cliente informou, só dígitos com DDD.",
            "example": "11999990000"
          },
          "extension": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ramal. A API ainda não preenche; chega como `null`."
          }
        }
      },
      "OrderItem": {
        "type": "object",
        "title": "OrderItem",
        "description": "Um produto do pedido com as opções escolhidas. Em produto com variações de tamanho, a variação vem embutida em `name` e no `unitPrice`, e não em `options`.",
        "required": [
          "id",
          "index",
          "name",
          "externalCode",
          "unit",
          "ean",
          "quantity",
          "specialInstructions",
          "unitPrice",
          "optionsPrice",
          "totalPrice",
          "options"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador da linha do pedido.",
            "example": "f1e2d3c4-b5a6-4978-8a9b-0c1d2e3f4a5b"
          },
          "index": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Posição do item no pedido. A API ainda não preenche; chega como `null`."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do produto como foi vendido, incluindo a variação quando há (`\"Pizza Margherita Grande\"`).",
            "example": "Pizza Margherita Grande"
          },
          "externalCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id do produto no cardápio (`items[].id` de `getMenus`).",
            "example": "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d"
          },
          "unit": {
            "type": [
              "string",
              "null"
            ],
            "description": "Unidade de medida. A API ainda não preenche; chega como `null`."
          },
          "ean": {
            "type": [
              "string",
              "null"
            ],
            "description": "Código de barras. A API ainda não preenche; chega como `null`."
          },
          "quantity": {
            "type": "number",
            "description": "Quantidade.",
            "example": 1
          },
          "specialInstructions": {
            "type": [
              "string",
              "null"
            ],
            "description": "Observação do cliente para este item.",
            "example": "Sem manjericão"
          },
          "unitPrice": {
            "$ref": "#/components/schemas/Money"
          },
          "optionsPrice": {
            "$ref": "#/components/schemas/Money"
          },
          "totalPrice": {
            "$ref": "#/components/schemas/Money"
          },
          "options": {
            "type": "array",
            "description": "Complementos escolhidos. Vazio quando não há.",
            "items": {
              "$ref": "#/components/schemas/OrderItemOption"
            }
          }
        }
      },
      "OrderItemOption": {
        "type": "object",
        "title": "OrderItemOption",
        "description": "Um complemento escolhido dentro de um item.",
        "required": [
          "id",
          "index",
          "name",
          "externalCode",
          "quantity",
          "unit",
          "unitPrice",
          "price",
          "groupName"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador da linha da opção.",
            "example": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"
          },
          "index": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Posição da opção dentro do item. A API ainda não preenche; chega como `null`."
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do complemento.",
            "example": "Borda recheada"
          },
          "externalCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "Id da opção no cardápio (`optionGroups[].options[].id` de `getMenus`).",
            "example": "b8a7c6d5-e4f3-4210-9876-543210fedcba"
          },
          "quantity": {
            "type": "integer",
            "description": "Quantidade.",
            "example": 1
          },
          "unit": {
            "type": [
              "string",
              "null"
            ],
            "description": "Unidade de medida. A API ainda não preenche; chega como `null`."
          },
          "unitPrice": {
            "$ref": "#/components/schemas/Money"
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          },
          "groupName": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do grupo de complementos. A API ainda não preenche; chega como `null`."
          }
        }
      },
      "OtherFee": {
        "type": "object",
        "title": "OtherFee",
        "description": "Uma taxa além dos itens.",
        "required": [
          "name",
          "type",
          "receivedBy",
          "price"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Nome da taxa, por exemplo \"Taxa de entrega\" ou o motivo do acréscimo.",
            "example": "Taxa de entrega"
          },
          "type": {
            "type": "string",
            "description": "`DELIVERY` para taxa de entrega; `SERVICE_FEE` para acréscimo.",
            "enum": [
              "DELIVERY",
              "SERVICE_FEE"
            ]
          },
          "receivedBy": {
            "type": "string",
            "description": "Quem recebe. Sempre a loja.",
            "enum": [
              "MERCHANT"
            ]
          },
          "price": {
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "Discount": {
        "type": "object",
        "title": "Discount",
        "description": "Um desconto aplicado ao pedido.",
        "required": [
          "amount",
          "target",
          "targetId",
          "sponsorshipValues"
        ],
        "properties": {
          "amount": {
            "$ref": "#/components/schemas/Money"
          },
          "target": {
            "type": "string",
            "description": "O desconto incide sobre o carrinho inteiro.",
            "enum": [
              "CART"
            ]
          },
          "targetId": {
            "type": [
              "string",
              "null"
            ],
            "description": "Não usado em desconto de carrinho; chega como `null`."
          },
          "sponsorshipValues": {
            "type": "array",
            "description": "Quem banca o desconto. Desconto do MeuPedido é sempre da loja.",
            "items": {
              "$ref": "#/components/schemas/SponsorshipValue"
            }
          }
        }
      },
      "SponsorshipValue": {
        "type": "object",
        "title": "SponsorshipValue",
        "description": "Parcela do desconto bancada por uma das partes.",
        "required": [
          "name",
          "amount"
        ],
        "properties": {
          "name": {
            "type": "string",
            "description": "Quem banca. Sempre a loja.",
            "enum": [
              "MERCHANT"
            ]
          },
          "amount": {
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "OrderTotal": {
        "type": "object",
        "title": "OrderTotal",
        "description": "Totais do pedido. `orderAmount` = `items` + `otherFees` - `discount`.",
        "required": [
          "items",
          "otherFees",
          "discount",
          "orderAmount"
        ],
        "properties": {
          "items": {
            "$ref": "#/components/schemas/Money"
          },
          "otherFees": {
            "$ref": "#/components/schemas/Money"
          },
          "discount": {
            "$ref": "#/components/schemas/Money"
          },
          "orderAmount": {
            "$ref": "#/components/schemas/Money"
          }
        }
      },
      "OrderPayments": {
        "type": "object",
        "title": "OrderPayments",
        "description": "Pagamentos do pedido.",
        "required": [
          "prepaid",
          "pending",
          "methods"
        ],
        "properties": {
          "prepaid": {
            "type": "number",
            "description": "Soma dos pagamentos `ONLINE` (já pagos).",
            "example": 0
          },
          "pending": {
            "type": "number",
            "description": "Soma dos pagamentos `OFFLINE` (a cobrar na entrega ou no balcão).",
            "example": 64.9
          },
          "methods": {
            "type": "array",
            "description": "Um item por pagamento.",
            "items": {
              "$ref": "#/components/schemas/PaymentMethod"
            }
          }
        }
      },
      "PaymentMethod": {
        "type": "object",
        "title": "PaymentMethod",
        "description": "Um pagamento do pedido.",
        "required": [
          "value",
          "currency",
          "method",
          "methodInfo",
          "type",
          "changeFor",
          "brand",
          "transaction"
        ],
        "properties": {
          "value": {
            "type": "number",
            "description": "Valor deste pagamento.",
            "example": 64.9
          },
          "currency": {
            "type": "string",
            "description": "Código ISO 4217. Sempre `BRL`.",
            "pattern": "^[A-Z]{3}$",
            "example": "BRL"
          },
          "method": {
            "type": "string",
            "description": "Meio de pagamento.",
            "enum": [
              "CREDIT",
              "DEBIT",
              "CASH",
              "PIX",
              "MEAL_VOUCHER",
              "OTHER"
            ]
          },
          "methodInfo": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do meio como a loja cadastrou.",
            "example": "Cartão de crédito"
          },
          "type": {
            "type": "string",
            "description": "`ONLINE` quando já foi pago; `OFFLINE` quando a loja cobra. Deriva do pagamento efetivo, não do meio: um PIX pago na porta é `OFFLINE`.",
            "enum": [
              "ONLINE",
              "OFFLINE"
            ]
          },
          "changeFor": {
            "type": [
              "number",
              "null"
            ],
            "description": "Em dinheiro, o valor com que o cliente vai pagar, para calcular o troco. `null` sem troco."
          },
          "brand": {
            "type": [
              "string",
              "null"
            ],
            "description": "Bandeira do cartão, quando informada."
          },
          "transaction": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/PaymentTransaction"
              },
              {
                "type": "null"
              }
            ],
            "description": "Dados da transação em pagamento on-line. `null` nos demais."
          }
        }
      },
      "PaymentTransaction": {
        "type": "object",
        "title": "PaymentTransaction",
        "description": "Transação de um pagamento on-line.",
        "required": [
          "transactionId",
          "authorizationCode",
          "acquirerDocument"
        ],
        "properties": {
          "transactionId": {
            "type": "string",
            "description": "Identificador da transação no provedor de pagamento.",
            "example": "E18236120202609191432s0f3c9a1b2c"
          },
          "authorizationCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "NSU ou código de autorização, quando existe."
          },
          "acquirerDocument": {
            "type": [
              "string",
              "null"
            ],
            "description": "Documento do adquirente. A API ainda não preenche; chega como `null`."
          }
        }
      },
      "DeliveryInfo": {
        "type": "object",
        "title": "DeliveryInfo",
        "description": "Dados de entrega. Presente apenas em pedido `DELIVERY`.",
        "required": [
          "deliveryDateTime",
          "estimatedDeliveryDateTime",
          "deliveredBy",
          "deliveryAddress"
        ],
        "properties": {
          "deliveryDateTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Quando foi entregue. A API ainda não preenche; chega como `null`."
          },
          "estimatedDeliveryDateTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Previsão de entrega. A API ainda não preenche; chega como `null`."
          },
          "deliveredBy": {
            "type": "string",
            "description": "Quem entrega. `MERCHANT` para a própria loja; `MARKETPLACE` quando o canal de origem entrega.",
            "enum": [
              "MERCHANT",
              "MARKETPLACE"
            ]
          },
          "deliveryAddress": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/DeliveryAddress"
              },
              {
                "type": "null"
              }
            ],
            "description": "Endereço de entrega. `null` quando o pedido não tem logradouro."
          }
        }
      },
      "DeliveryAddress": {
        "type": "object",
        "title": "DeliveryAddress",
        "description": "Endereço de entrega.",
        "required": [
          "country",
          "state",
          "city",
          "district",
          "street",
          "number",
          "postalCode",
          "complement",
          "reference",
          "formattedAddress",
          "coordinates"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "País, ISO 3166-1 alpha-2. `BR`.",
            "example": "BR"
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "UF.",
            "example": "SP"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cidade.",
            "example": "São Paulo"
          },
          "district": {
            "type": [
              "string",
              "null"
            ],
            "description": "Bairro.",
            "example": "Pinheiros"
          },
          "street": {
            "type": "string",
            "description": "Logradouro.",
            "example": "Rua dos Pinheiros"
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número.",
            "example": "1000"
          },
          "postalCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "CEP, como o cliente informou.",
            "example": "05422001"
          },
          "complement": {
            "type": [
              "string",
              "null"
            ],
            "description": "Complemento.",
            "example": "Apto 42"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ],
            "description": "Ponto de referência."
          },
          "formattedAddress": {
            "type": [
              "string",
              "null"
            ],
            "description": "Endereço em uma linha. A API ainda não preenche; chega como `null`."
          },
          "coordinates": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Coordinates"
              },
              {
                "type": "null"
              }
            ],
            "description": "Coordenadas em graus decimais. `null` quando o endereço não foi geocodificado."
          }
        }
      },
      "Coordinates": {
        "type": "object",
        "title": "Coordinates",
        "description": "Coordenadas geográficas em graus decimais.",
        "required": [
          "latitude",
          "longitude"
        ],
        "properties": {
          "latitude": {
            "type": "number",
            "description": "Latitude, de -90 a 90.",
            "minimum": -90,
            "maximum": 90,
            "example": -23.5656
          },
          "longitude": {
            "type": "number",
            "description": "Longitude, de -180 a 180.",
            "minimum": -180,
            "maximum": 180,
            "example": -46.6898
          }
        }
      },
      "TakeoutInfo": {
        "type": "object",
        "title": "TakeoutInfo",
        "description": "Dados de retirada. Presente em pedido `TAKEOUT` e `INDOOR`.",
        "required": [
          "mode",
          "takeoutDateTime"
        ],
        "properties": {
          "mode": {
            "type": "string",
            "description": "Modo de retirada. Sempre `DEFAULT`.",
            "enum": [
              "DEFAULT"
            ]
          },
          "takeoutDateTime": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Horário agendado da retirada. `null` em pedido imediato; o MeuPedido não inventa uma previsão."
          }
        }
      },
      "ScheduleInfo": {
        "type": "object",
        "title": "ScheduleInfo",
        "description": "Janela de agendamento. Presente em pedido `SCHEDULED`.",
        "required": [
          "scheduledDateTimeStart",
          "scheduledDateTimeEnd"
        ],
        "properties": {
          "scheduledDateTimeStart": {
            "type": "string",
            "format": "date-time",
            "description": "Início da janela agendada. Também em `preparationStartDateTime` e, em retirada, em `takeout.takeoutDateTime`.",
            "example": "2026-09-19T22:30:00Z"
          },
          "scheduledDateTimeEnd": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Fim da janela. A API ainda não preenche; chega como `null`."
          }
        }
      },
      "Merchant": {
        "type": "object",
        "title": "Merchant",
        "description": "Dados cadastrais da loja. Campo sem valor vem como `null`.",
        "required": [
          "id",
          "name",
          "description",
          "document",
          "status",
          "contactEmails",
          "contactPhones",
          "address",
          "services",
          "createdAt",
          "lastUpdate"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id da loja. É o `merchantId` da rota e a claim `merchant_id` do token.",
            "example": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome de exibição da loja.",
            "example": "Loja de teste Acme"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descrição livre, como aparece no cardápio digital."
          },
          "document": {
            "type": [
              "string",
              "null"
            ],
            "description": "CNPJ ou CPF da loja, só dígitos.",
            "example": "12345678000190"
          },
          "status": {
            "type": "string",
            "description": "`AVAILABLE` quando a loja pode receber pedidos agora; `UNAVAILABLE` quando está desativada ou fechada temporariamente. É status, não ausência: mostre \"fechado agora\".",
            "enum": [
              "AVAILABLE",
              "UNAVAILABLE"
            ]
          },
          "contactEmails": {
            "type": [
              "array",
              "null"
            ],
            "description": "E-mails de contato. `null` quando não há.",
            "items": {
              "type": "string"
            }
          },
          "contactPhones": {
            "type": [
              "array",
              "null"
            ],
            "description": "Telefones de contato. `null` quando não há.",
            "items": {
              "type": "string"
            }
          },
          "address": {
            "$ref": "#/components/schemas/MerchantAddress"
          },
          "services": {
            "type": "array",
            "description": "Modalidades de atendimento habilitadas. Vazio quando nenhuma está ligada.",
            "items": {
              "$ref": "#/components/schemas/MerchantService"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Criação da loja, UTC com `Z`.",
            "example": "2025-03-12T14:02:11Z"
          },
          "lastUpdate": {
            "type": "string",
            "format": "date-time",
            "description": "Última alteração dos dados da loja, UTC com `Z`. Compare para saber se algo mudou.",
            "example": "2026-09-18T21:45:09Z"
          }
        }
      },
      "MerchantAddress": {
        "type": "object",
        "title": "MerchantAddress",
        "description": "Endereço da loja.",
        "required": [
          "country",
          "state",
          "city",
          "district",
          "street",
          "number",
          "postalCode",
          "complement",
          "latitude",
          "longitude"
        ],
        "properties": {
          "country": {
            "type": "string",
            "description": "País, ISO 3166-1 alpha-2. `BR`.",
            "example": "BR"
          },
          "state": {
            "type": [
              "string",
              "null"
            ],
            "description": "UF.",
            "example": "SP"
          },
          "city": {
            "type": [
              "string",
              "null"
            ],
            "description": "Cidade.",
            "example": "São Paulo"
          },
          "district": {
            "type": [
              "string",
              "null"
            ],
            "description": "Bairro.",
            "example": "Pinheiros"
          },
          "street": {
            "type": [
              "string",
              "null"
            ],
            "description": "Logradouro.",
            "example": "Rua dos Pinheiros"
          },
          "number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Número.",
            "example": "1000"
          },
          "postalCode": {
            "type": [
              "string",
              "null"
            ],
            "description": "CEP.",
            "example": "05422001"
          },
          "complement": {
            "type": [
              "string",
              "null"
            ],
            "description": "Complemento."
          },
          "latitude": {
            "type": [
              "number",
              "null"
            ],
            "description": "Latitude em graus decimais. `null` quando a loja não foi geocodificada.",
            "example": -23.5656
          },
          "longitude": {
            "type": [
              "number",
              "null"
            ],
            "description": "Longitude em graus decimais.",
            "example": -46.6898
          }
        }
      },
      "MerchantService": {
        "type": "object",
        "title": "MerchantService",
        "description": "Uma modalidade de atendimento da loja.",
        "required": [
          "serviceType",
          "status",
          "menuId"
        ],
        "properties": {
          "serviceType": {
            "type": "string",
            "description": "`DELIVERY` entrega; `TAKEOUT` retirada; `INDOOR` consumo no local (mesa).",
            "enum": [
              "DELIVERY",
              "TAKEOUT",
              "INDOOR"
            ]
          },
          "status": {
            "type": "string",
            "description": "Disponibilidade da modalidade. Só modalidades ligadas aparecem, então hoje é sempre `AVAILABLE`.",
            "enum": [
              "AVAILABLE",
              "UNAVAILABLE"
            ]
          },
          "menuId": {
            "type": "string",
            "format": "uuid",
            "description": "Id do cardápio usado nesta modalidade. Igual ao `id` do elemento de `getMenus`.",
            "example": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
          }
        }
      },
      "Menu": {
        "type": "object",
        "title": "Menu",
        "description": "O cardápio completo, normalizado. Categorias apontam para itens por id; itens apontam para grupos de opções por id.",
        "required": [
          "id",
          "name",
          "categories",
          "items",
          "optionGroups"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id do cardápio. Igual ao `menuId` em `services` da loja.",
            "example": "6d2f4c8a-1b3e-4f5a-9c7d-2e8b0a1f3c5d"
          },
          "name": {
            "type": "string",
            "description": "Nome do cardápio.",
            "example": "Cardápio Loja de teste Acme"
          },
          "categories": {
            "type": "array",
            "description": "Categorias, na ordem de exibição.",
            "items": {
              "$ref": "#/components/schemas/MenuCategory"
            }
          },
          "items": {
            "type": "array",
            "description": "Todos os itens vendáveis do cardápio, inclusive os `UNAVAILABLE`.",
            "items": {
              "$ref": "#/components/schemas/MenuItem"
            }
          },
          "optionGroups": {
            "type": "array",
            "description": "Todos os grupos de opções referenciados pelos itens, inclusive os grupos \"Tamanho\" gerados a partir de variações.",
            "items": {
              "$ref": "#/components/schemas/OptionGroup"
            }
          }
        }
      },
      "MenuCategory": {
        "type": "object",
        "title": "MenuCategory",
        "description": "Uma seção do cardápio.",
        "required": [
          "id",
          "name",
          "description",
          "index",
          "status",
          "itemOfferIds"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id da categoria.",
            "example": "5b7e1f3a-9c2d-4e6b-8a1f-0d3c7e9b2a54"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome da categoria.",
            "example": "Pizzas"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descrição opcional."
          },
          "index": {
            "type": "integer",
            "format": "int32",
            "description": "Posição na ordem de exibição, a partir de 0.",
            "example": 0
          },
          "status": {
            "type": "string",
            "description": "Disponibilidade da categoria inteira. Categoria pausada continua no cardápio como `UNAVAILABLE`.",
            "enum": [
              "AVAILABLE",
              "UNAVAILABLE"
            ]
          },
          "itemOfferIds": {
            "type": "array",
            "description": "Ids dos itens que pertencem à categoria, na ordem de exibição.",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          }
        }
      },
      "MenuItem": {
        "type": "object",
        "title": "MenuItem",
        "description": "Um produto vendável. É o `externalCode` de `items[]` no pedido.",
        "required": [
          "id",
          "name",
          "description",
          "externalCode",
          "status",
          "image",
          "price",
          "optionGroupIds"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id do produto. Chega em `items[].externalCode` de um pedido.",
            "example": "3c2b1a09-8f7e-4d6c-b5a4-9382716f5e4d"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do produto.",
            "example": "Pizza Margherita"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descrição."
          },
          "externalCode": {
            "type": "string",
            "description": "Código do produto no cadastro do lojista (PLU, SKU). Quando a loja não cadastrou um código, é o próprio `id`.",
            "example": "PZ-001"
          },
          "status": {
            "type": "string",
            "description": "Disponibilidade. Produto pausado continua no cardápio como `UNAVAILABLE`, nunca some.",
            "enum": [
              "AVAILABLE",
              "UNAVAILABLE"
            ]
          },
          "image": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL da imagem, quando há.",
            "example": "https://cdn.meupedido.io/lojas/acme/pizza-margherita.jpg"
          },
          "price": {
            "$ref": "#/components/schemas/MenuItemPrice"
          },
          "optionGroupIds": {
            "type": [
              "array",
              "null"
            ],
            "description": "Ids dos grupos de opções que o item oferece, na ordem de exibição. O grupo \"Tamanho\" (`{productId}-variacoes`) vem primeiro quando existe. `null` quando o item não tem grupos.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "MenuItemPrice": {
        "type": "object",
        "title": "MenuItemPrice",
        "description": "Preço de um item ou de uma opção do cardápio.",
        "required": [
          "value",
          "originalValue",
          "currency"
        ],
        "properties": {
          "value": {
            "type": "number",
            "description": "Preço atual. Em promoção, é o preço promocional. Em produto com variações, é o da variação mais barata (a primeira do grupo \"Tamanho\").",
            "example": 49.9
          },
          "originalValue": {
            "type": [
              "number",
              "null"
            ],
            "description": "Preço sem promoção, só quando o item está em promoção; `null` fora de promoção."
          },
          "currency": {
            "type": "string",
            "description": "Código ISO 4217. Sempre `BRL`.",
            "pattern": "^[A-Z]{3}$",
            "example": "BRL"
          }
        }
      },
      "OptionGroup": {
        "type": "object",
        "title": "OptionGroup",
        "description": "Um grupo de opções de um item. O grupo cujo id termina em `-variacoes` representa o próprio produto (tamanho), não um acréscimo.",
        "required": [
          "id",
          "name",
          "description",
          "index",
          "status",
          "minPermitted",
          "maxPermitted",
          "options"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Id do grupo. GUID, ou `{productId}-variacoes` no grupo \"Tamanho\" gerado a partir de variações.",
            "example": "2c8e4b1d-7f3a-4d9e-a5c6-1b0f8d3e7a92"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome do grupo, como \"Tamanho\" ou \"Adicionais\".",
            "example": "Adicionais"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "description": "Descrição opcional."
          },
          "index": {
            "type": "integer",
            "format": "int32",
            "description": "Posição na ordem de exibição.",
            "example": 1
          },
          "status": {
            "type": "string",
            "description": "Disponibilidade do grupo.",
            "enum": [
              "AVAILABLE",
              "UNAVAILABLE"
            ]
          },
          "minPermitted": {
            "type": "integer",
            "format": "int32",
            "description": "Quantidade mínima de opções que o cliente precisa escolher. `1` ou mais torna o grupo obrigatório.",
            "minimum": 0,
            "example": 0
          },
          "maxPermitted": {
            "type": "integer",
            "format": "int32",
            "description": "Quantidade máxima de opções. Sem máximo cadastrado, é o total de opções do grupo.",
            "minimum": 0,
            "example": 3
          },
          "options": {
            "type": "array",
            "description": "Opções do grupo, na ordem de exibição.",
            "items": {
              "$ref": "#/components/schemas/MenuOption"
            }
          }
        }
      },
      "MenuOption": {
        "type": "object",
        "title": "MenuOption",
        "description": "Uma opção dentro de um grupo. É o `externalCode` de `items[].options[]` no pedido.",
        "required": [
          "id",
          "name",
          "externalCode",
          "index",
          "status",
          "price"
        ],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid",
            "description": "Id da opção. Chega em `items[].options[].externalCode` de um pedido.",
            "example": "b8a7c6d5-e4f3-4210-9876-543210fedcba"
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Nome da opção.",
            "example": "Borda recheada"
          },
          "externalCode": {
            "type": "string",
            "description": "Código da opção. Hoje é o próprio `id`.",
            "example": "b8a7c6d5-e4f3-4210-9876-543210fedcba"
          },
          "index": {
            "type": "integer",
            "format": "int32",
            "description": "Posição na ordem de exibição.",
            "example": 0
          },
          "status": {
            "type": "string",
            "description": "Disponibilidade da opção.",
            "enum": [
              "AVAILABLE",
              "UNAVAILABLE"
            ]
          },
          "price": {
            "$ref": "#/components/schemas/MenuItemPrice"
          }
        }
      },
      "EventEnvelope": {
        "type": "object",
        "title": "EventEnvelope",
        "description": "O aviso de que algo aconteceu com um pedido. Nunca traz o pedido: busque em `orderURL`.\nOs cinco campos obrigatórios são os da especificação. `sourceAppId`, `metadata` e\n`delivery` só aparecem quando têm valor; desserialize com tolerância a campos ausentes e\na campos novos. O mesmo `eventId` pode chegar mais de uma vez: use-o como chave única.\n",
        "required": [
          "eventId",
          "eventType",
          "orderId",
          "orderURL",
          "createdAt"
        ],
        "properties": {
          "eventId": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador único do evento. É o que você confirma em `acknowledgment` e a sua chave de deduplicação.",
            "example": "c0a8f1e2-3d4b-4c5a-9e6f-7a8b9c0d1e2f"
          },
          "eventType": {
            "type": "string",
            "description": "O tipo do evento. Os seis primeiros são os do padrão 1.4.0 que o MeuPedido emite; os\ndemais são extensões MeuPedido (não existem no padrão 1.4.0). Um consumidor aderente\nestrito ignora o tipo que não conhece. `PICKUP_AREA_ASSIGNED`, `DELIVERED`,\n`CANCELLATION_REQUESTED` e `CANCELLATION_REQUEST_DENIED`, previstos no padrão, nunca\nsão emitidos: a entrega é sinalizada por `CONCLUDED` e o cancelamento é imediato.\n\n- `CREATED`: o pedido entrou em `PENDING` (criado, ou PIX confirmado). Busque em `orderURL`.\n- `CONFIRMED`: a loja aceitou (`ACCEPTED`). `metadata` vem como `{}`.\n- `READY_FOR_PICKUP`: pronto (`READY`). Em `TAKEOUT` e `INDOOR`, também ao entrar em `DELIVERY`.\n- `DISPATCHED`: saiu para entrega (`DELIVERY`) em pedido `DELIVERY`.\n- `CONCLUDED`: entregue ou retirado (`DONE`).\n- `CANCELLED`: cancelado por qualquer caminho. `metadata.reason` e `metadata.code`.\n- `PREPARING` (extensão): a cozinha começou (`PREPARING`).\n- `MODIFIED` (extensão): o conteúdo do pedido mudou. Busque de novo em `orderURL` e substitua a cópia inteira.\n- `COURIER_ASSIGNED` (extensão): um entregador ficou com o pedido (rota criada). Traz `delivery`.\n- `COURIER_ROUTE_STARTED` (extensão): o entregador começou a rota. Traz `delivery`.\n- `COURIER_PICKED_UP` (extensão): o entregador retirou o pedido na loja. Traz `delivery`.\n- `COURIER_ARRIVED` (extensão): o entregador chegou ao endereço do cliente. Traz `delivery`.\n- `COURIER_DELIVERED` (extensão): o entregador confirmou a entrega. Traz `delivery`.\n- `COURIER_DELIVERY_FAILED` (extensão): a entrega falhou; motivo em `delivery.failureReason`.\n- `COURIER_UNASSIGNED` (extensão): o entregador devolveu o pedido antes de tentar entregar. Traz `delivery`.\n- `COURIER_ROUTE_COMPLETED` (extensão): a rota inteira terminou; sai para cada pedido dela. Traz `delivery`.\n",
            "enum": [
              "CREATED",
              "CONFIRMED",
              "READY_FOR_PICKUP",
              "DISPATCHED",
              "CONCLUDED",
              "CANCELLED",
              "PREPARING",
              "MODIFIED",
              "COURIER_ASSIGNED",
              "COURIER_ROUTE_STARTED",
              "COURIER_PICKED_UP",
              "COURIER_ARRIVED",
              "COURIER_DELIVERED",
              "COURIER_DELIVERY_FAILED",
              "COURIER_UNASSIGNED",
              "COURIER_ROUTE_COMPLETED"
            ],
            "x-enumDescriptions": {
              "CREATED": "O pedido entrou em PENDING. Busque em orderURL.",
              "CONFIRMED": "A loja aceitou (ACCEPTED). metadata vem como {}.",
              "READY_FOR_PICKUP": "Pronto para sair ou para retirada (READY).",
              "DISPATCHED": "Saiu para entrega (DELIVERY) em pedido DELIVERY.",
              "CONCLUDED": "Entregue ou retirado (DONE).",
              "CANCELLED": "Cancelado. metadata.reason e metadata.code.",
              "PREPARING": "Extensão MeuPedido: a cozinha começou (PREPARING).",
              "MODIFIED": "Extensão MeuPedido: o conteúdo do pedido mudou; busque de novo em orderURL.",
              "COURIER_ASSIGNED": "Extensão MeuPedido: um entregador ficou com o pedido. Traz delivery.",
              "COURIER_ROUTE_STARTED": "Extensão MeuPedido: o entregador começou a rota. Traz delivery.",
              "COURIER_PICKED_UP": "Extensão MeuPedido: o entregador retirou o pedido na loja. Traz delivery.",
              "COURIER_ARRIVED": "Extensão MeuPedido: o entregador chegou ao cliente. Traz delivery.",
              "COURIER_DELIVERED": "Extensão MeuPedido: o entregador confirmou a entrega. Traz delivery.",
              "COURIER_DELIVERY_FAILED": "Extensão MeuPedido: a entrega falhou; motivo em delivery.failureReason.",
              "COURIER_UNASSIGNED": "Extensão MeuPedido: o entregador devolveu o pedido. Traz delivery.",
              "COURIER_ROUTE_COMPLETED": "Extensão MeuPedido: a rota terminou; sai para cada pedido dela. Traz delivery."
            },
            "x-meupedido-extensions": [
              "PREPARING",
              "MODIFIED",
              "COURIER_ASSIGNED",
              "COURIER_ROUTE_STARTED",
              "COURIER_PICKED_UP",
              "COURIER_ARRIVED",
              "COURIER_DELIVERED",
              "COURIER_DELIVERY_FAILED",
              "COURIER_UNASSIGNED",
              "COURIER_ROUTE_COMPLETED"
            ]
          },
          "orderId": {
            "type": "string",
            "format": "uuid",
            "description": "O pedido a que o evento se refere.",
            "example": "9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e"
          },
          "orderURL": {
            "type": "string",
            "format": "uri",
            "description": "URL absoluta de `GET /v1/orders/{orderId}`. Use com o mesmo token.",
            "example": "https://api.meupedido.io/open-delivery/v1/orders/9b1e2d3c-4f5a-4b6c-8d7e-0f1a2b3c4d5e"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "Instante em que o evento foi gerado, UTC com `Z`.",
            "example": "2026-09-19T14:32:10Z"
          },
          "sourceAppId": {
            "type": "string",
            "description": "Identificador da aplicação de pedidos de origem, para quem consome vários fornecedores pelo mesmo cano (hub). Omitido quando não configurado."
          },
          "metadata": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/EventMetadataCancel"
              },
              {
                "$ref": "#/components/schemas/EventMetadataConfirm"
              }
            ],
            "description": "Presente por tipo. `CANCELLED` traz `reason` e `code`; `CONFIRMED` traz `{}`, porque o padrão exige o campo presente para este tipo. Os demais tipos não trazem o campo."
          },
          "delivery": {
            "$ref": "#/components/schemas/CourierDelivery"
          }
        }
      },
      "EventMetadataCancel": {
        "type": "object",
        "title": "EventMetadataCancel",
        "description": "`metadata` do evento `CANCELLED`.",
        "required": [
          "reason",
          "code"
        ],
        "properties": {
          "reason": {
            "type": "string",
            "description": "O motivo registrado por quem cancelou. Pela API, é o `reason` de `requestCancellation` (ou o padrão); pelo painel, o que o lojista escreveu; sem motivo, \"Cancelado pela loja.\".",
            "example": "Cliente desistiu do pedido."
          },
          "code": {
            "type": "string",
            "description": "`CONSUMER_CANCELLATION_REQUESTED` quando o cliente cancelou; `OTHER_CANCELLATION_REASON` para loja, painel e API. O `code` enviado em `requestCancellation` não influencia este valor.",
            "enum": [
              "CONSUMER_CANCELLATION_REQUESTED",
              "OTHER_CANCELLATION_REASON"
            ]
          }
        }
      },
      "EventMetadataConfirm": {
        "type": "object",
        "title": "EventMetadataConfirm",
        "description": "`metadata` do evento `CONFIRMED`. Objeto vazio: o padrão exige o campo presente para este tipo, e não há nada obrigatório dentro.",
        "properties": {},
        "additionalProperties": false
      },
      "CourierDelivery": {
        "type": "object",
        "title": "CourierDelivery",
        "description": "Extensão MeuPedido. A rota e a parada do pedido dentro dela; presente apenas nos eventos `COURIER_*`. Campos sem valor são omitidos.",
        "required": [
          "routeId",
          "courierId"
        ],
        "properties": {
          "routeId": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador da rota. Uma rota pode ter vários pedidos.",
            "example": "b4c1d2e3-f4a5-4b6c-8d7e-9f0a1b2c3d4e"
          },
          "routeCode": {
            "type": "string",
            "description": "Código curto da rota, o mesmo que o lojista vê no painel.",
            "example": "R-0217"
          },
          "courierId": {
            "type": "string",
            "format": "uuid",
            "description": "Identificador do entregador.",
            "example": "c9d8e7f6-a5b4-4c3d-9e2f-1a0b9c8d7e6f"
          },
          "courierName": {
            "type": "string",
            "description": "Nome do entregador, para a tela do caixa. Sem telefone, de propósito.",
            "example": "Carlos Andrade"
          },
          "stopStatus": {
            "type": "string",
            "description": "Situação da parada deste pedido na rota. Só nos eventos de parada.",
            "enum": [
              "PENDING",
              "EN_ROUTE",
              "ARRIVED",
              "DELIVERED",
              "FAILED",
              "UNASSIGNED"
            ]
          },
          "sequence": {
            "type": "integer",
            "format": "int32",
            "description": "Posição da parada na rota, a partir de 1.",
            "minimum": 1,
            "example": 2
          },
          "failureReason": {
            "type": "string",
            "description": "Motivo da falha, em `COURIER_DELIVERY_FAILED`."
          },
          "etaAt": {
            "type": "string",
            "format": "date-time",
            "description": "Previsão de chegada, UTC com `Z`, quando calculada.",
            "example": "2026-09-19T15:05:00Z"
          },
          "claimSource": {
            "type": "string",
            "description": "Como o entregador assumiu a rota. `COURIER_PULL` quando ele puxou; `MERCHANT_DISPATCH` quando a loja despachou.",
            "enum": [
              "COURIER_PULL",
              "MERCHANT_DISPATCH"
            ]
          }
        }
      },
      "WebhookEvent": {
        "title": "WebhookEvent",
        "description": "O corpo de um webhook. É o mesmo envelope do polling, byte a byte; um único desserializador atende os dois modos.",
        "allOf": [
          {
            "$ref": "#/components/schemas/EventEnvelope"
          }
        ]
      }
    }
  }
}
