{
  "info": {
    "name": "Qreaty — External API",
    "description": "Qreaty external API: menu (SKU), tables, orders, bills, order webhook.\n\n1. Issue a token: Settings → POS → API access.\n2. Put it into the collection variable `token`; set `base_url` for your environment.\n3. Run Menu items, Menu modifiers and List tables — they fill the working variables (sku, option_sku, table), which then appear in the list.\n\nDocs: /docs → External system (API).",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{token}}",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://qreaty.com/api/external/v1",
      "description": "Local: http://localhost:8088/api/external/v1 — no trailing slash"
    },
    {
      "key": "token",
      "value": "",
      "description": "Token from Settings → POS → API access"
    }
  ],
  "item": [
    {
      "name": "Menu",
      "item": [
        {
          "name": "Menu items",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/menu/items?per_page=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "menu",
                "items"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "50"
                },
                {
                  "key": "locale",
                  "value": "vi",
                  "disabled": true
                },
                {
                  "key": "filter[available]",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "filter[sku]",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "filter[menu]",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "filter[updated_since]",
                  "value": "2026-10-14T00:00:00Z",
                  "disabled": true
                }
              ]
            },
            "description": "Dishes of the ACTIVE menu, keyed by SKU (also the id). available = can be ordered right now. meta.menus lists the menus (use filter[menu] for another one) and the languages (locales) each can be read in. Enable the filters you need with the checkboxes. Saves the first SKU into the sku variable for Place order."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "pm.test('every dish has a SKU', () => b.data.every(d => d.attributes.sku));",
                  "if (b.data.length) { pm.collectionVariables.set('sku', b.data[0].attributes.sku); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Menu modifiers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/menu/modifiers?per_page=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "menu",
                "modifiers"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "50"
                },
                {
                  "key": "locale",
                  "value": "vi",
                  "disabled": true
                },
                {
                  "key": "filter[menu]",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "filter[updated_since]",
                  "value": "2026-10-14T00:00:00Z",
                  "disabled": true
                }
              ]
            },
            "description": "Modifier groups of the active menu with their options, keyed by SKU. Saves the first option SKU into option_sku for Place order."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "pm.test('every option has a SKU', () => b.data.every(g => g.attributes.options.every(o => o.sku)));",
                  "const g = b.data.find(g => g.attributes.options.length);",
                  "if (g) { pm.collectionVariables.set('option_sku', g.attributes.options[0].sku); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Tables",
      "item": [
        {
          "name": "List tables",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/tables",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "tables"
              ]
            },
            "description": "Tables in service. id is what an order's table carries (and what filter[table] takes); number, name and zone are how a person knows the table, for mapping your tables onto ours. Saves the first id into the table variable."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "if (b.data.length) { pm.collectionVariables.set('table', b.data[0].id); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Orders",
      "item": [
        {
          "name": "List orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/orders?per_page=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "orders"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "50"
                },
                {
                  "key": "filter[status]",
                  "value": "confirmed,completed",
                  "disabled": true
                },
                {
                  "key": "filter[source]",
                  "value": "external",
                  "disabled": true
                },
                {
                  "key": "filter[table]",
                  "value": "6512",
                  "disabled": true
                },
                {
                  "key": "filter[external_id]",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "filter[placed_from]",
                  "value": "2026-10-14T00:00:00%2B07:00",
                  "disabled": true
                },
                {
                  "key": "filter[placed_to]",
                  "value": "2026-10-15T00:00:00%2B07:00",
                  "disabled": true
                },
                {
                  "key": "filter[updated_since]",
                  "value": "2026-10-14T09:00:00Z",
                  "disabled": true
                },
                {
                  "key": "sort",
                  "value": "updated_at",
                  "disabled": true
                }
              ]
            },
            "description": "Orders with lines and options. Filters are switched off: enable the ones you need. Times are ISO 8601; write a + offset as %2B (e.g. 2026-10-14T00:00:00%2B07:00) or use Z."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "if (b.data.length) { pm.collectionVariables.set('order_id', b.data[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Get order",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/orders/{{order_id}}",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "orders",
                "{{order_id}}"
              ]
            },
            "description": "order_id is set by List orders or Place order."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Place order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/orders",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "orders"
              ]
            },
            "description": "The order webhook. Run Menu items, Menu modifiers and List tables first: they fill sku, option_sku and table (the table's id). If the dish has no required options, delete the modifiers block. external_id is generated on every send; to test the replay (must answer 200 with the same order) create the collection variable keep_external_id with any value and send again.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"external_id\": \"{{external_id}}\",\n  \"table\": \"{{table}}\",\n  \"room_number\": \"412\",\n  \"note\": \"Postman test\",\n  \"items\": [\n    {\n      \"sku\": \"{{sku}}\",\n      \"quantity\": 1,\n      \"modifiers\": [\n        {\n          \"sku\": \"{{option_sku}}\"\n        }\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (!pm.collectionVariables.get('keep_external_id') || !pm.collectionVariables.get('external_id')) {",
                  "  pm.collectionVariables.set('external_id', 'PM-' + Date.now());",
                  "}"
                ]
              }
            },
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('created (201) or replayed (200)', () => [200, 201].includes(pm.response.code));",
                  "const b = pm.response.json();",
                  "if (b.data) { pm.collectionVariables.set('order_id', b.data.id); }"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Bills",
      "item": [
        {
          "name": "List bills",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bills?per_page=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bills"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "50"
                },
                {
                  "key": "filter[status]",
                  "value": "closed",
                  "disabled": true
                },
                {
                  "key": "filter[tender]",
                  "value": "room_charge",
                  "disabled": true
                },
                {
                  "key": "filter[table]",
                  "value": "6512",
                  "disabled": true
                },
                {
                  "key": "filter[room_number]",
                  "value": "412",
                  "disabled": true
                },
                {
                  "key": "filter[number]",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "filter[opened_from]",
                  "value": "2026-10-14T00:00:00%2B07:00",
                  "disabled": true
                },
                {
                  "key": "filter[opened_to]",
                  "value": "2026-10-15T00:00:00%2B07:00",
                  "disabled": true
                },
                {
                  "key": "filter[closed_from]",
                  "value": "2026-10-14T00:00:00%2B07:00",
                  "disabled": true
                },
                {
                  "key": "filter[closed_to]",
                  "value": "2026-10-15T00:00:00%2B07:00",
                  "disabled": true
                },
                {
                  "key": "filter[updated_since]",
                  "value": "2026-10-14T09:00:00Z",
                  "disabled": true
                },
                {
                  "key": "embed",
                  "value": "orders",
                  "disabled": true
                },
                {
                  "key": "sort",
                  "value": "-closed_at",
                  "disabled": true
                }
              ]
            },
            "description": "Bills. embed=orders returns the orders whole. tender: cash, transfer, card, ewallet, room_charge, voucher, none."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "if (b.data.length) { pm.collectionVariables.set('bill_id', b.data[0].id); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Get bill",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/bills/{{bill_id}}?embed=orders",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "bills",
                "{{bill_id}}"
              ],
              "query": [
                {
                  "key": "embed",
                  "value": "orders"
                }
              ]
            },
            "description": "bill_id is set by List bills."
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status is 200', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}