{
  "info": {
    "_postman_id": "6f2d8e4a-3b1c-4d5e-9a7f-0c2e4b6d8f10",
    "name": "RapidHub POS API",
    "description": "Requests for POS vendors integrating with RapidHub.\n\nSet the environment variables `baseUrl` (https://hub.rapidpaygroup.com) and `restaurantKey` (shown once when you create a restaurant in the dashboard). Real-time orders arrive over SignalR (`/hubs/orders`), which plain HTTP requests do not cover; see https://hub.rapidpaygroup.com/integration/documentation#realtime.\n\nOpenAPI: https://hub.rapidpaygroup.com/integration/openapi.json",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-Restaurant-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{restaurantKey}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "item": [
    {
      "name": "Auth check",
      "description": "Checks that `restaurantKey` is valid. Every POS endpoint answers **401 with an empty body** for a missing, unknown or inactive key.",
      "item": [
        {
          "name": "Verify restaurant key",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/connections",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "connections"
              ]
            },
            "description": "Cheapest authenticated call. 200 = the key is valid (the list may be empty). 401 = wrong or inactive key."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('Restaurant key accepted', () => pm.response.to.have.status(200));"
                ]
              }
            }
          ]
        },
        {
          "name": "Without a key -> 401",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/active",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "active"
              ]
            },
            "description": "Same call without credentials: expect 401.",
            "auth": {
              "type": "noauth"
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 401', () => pm.response.to.have.status(401));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Orders",
      "description": "Active orders and state changes. `orderId` is the RapidHub order id (`id`), not `platformOrderId`.\n\nAllowed transitions: Received → Accepted | Rejected | Canceled; Accepted → Preparing | Ready | Dispatched | Delivered | Canceled; Preparing → Ready | Dispatched | Delivered | Canceled; Ready → Dispatched | Delivered | Canceled; Dispatched → Delivered | Canceled. `Dispatched` is for delivery orders only.",
      "item": [
        {
          "name": "List active orders",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/active",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "active"
              ]
            },
            "description": "Non-terminal orders of this restaurant, oldest first. Stores the id of the first `Received` order (else the first order) in `orderId`."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const orders = pm.response.code === 200 ? pm.response.json() : [];",
                  "pm.test('Active orders is an array', () => pm.expect(orders).to.be.an('array'));",
                  "const next = orders.find(o => o.state === 'Received') || orders[0];",
                  "if (next) { pm.environment.set('orderId', next.id); console.log('orderId =', next.id, next.platform, next.displayCode, next.state); }",
                  "else { console.log('No active orders - send a test order from the dashboard Sandbox page first.'); }"
                ]
              }
            }
          ]
        },
        {
          "name": "Accept (prep time 20 min)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Accept and tell the platform the order is ready in 20 minutes (where supported, see capabilities.supportsPrepTime).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Accepted\",\n  \"prepTimeMinutes\": 20\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Accept (platform default time)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Accept without a preparation time.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Accepted\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Reject (items unavailable)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Only from Received. rejectCode: Other, ItemsUnavailable, TooBusy, Closing, Closed.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Rejected\",\n  \"rejectCode\": \"ItemsUnavailable\",\n  \"reason\": \"Sorry, the cheeseburger is sold out.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Preparing",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Optional step after Accepted.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Preparing\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Ready",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Food is ready for the courier / customer.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Ready\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Dispatched (own courier left)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Delivery orders only, mainly for courierType Self. Platform couriers are tracked by the platform.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Dispatched\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Delivered",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Handed over to the customer (pickup) or delivered by your own courier.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Delivered\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "Cancel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Cancel a non-terminal order. Some platforms have no venue-cancel API after acceptance; the cancel is then only applied in RapidHub.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Canceled\",\n  \"reason\": \"Customer asked to cancel\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Catalog",
      "description": "Menu push to every connected platform at once. Prices are gross minor units (cents).",
      "item": [
        {
          "name": "List connections",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/connections",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "connections"
              ]
            },
            "description": "Platform connections with capabilities. Stores the Mock connection (else the first) in `connectionId` if it is empty."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const list = pm.response.json();",
                  "const mock = list.find(c => c.platform === 'Mock' && c.isActive);",
                  "const pick = mock || list[0];",
                  "if (pick && !pm.environment.get('connectionId')) pm.environment.set('connectionId', pick.id);",
                  "console.log('connections:', list.map(c => c.platform + ' ' + c.platformStoreId + ' ' + c.id).join(' | '));"
                ]
              }
            }
          ]
        },
        {
          "name": "Push sample menu",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/push",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "push"
              ]
            },
            "description": "Full menu: 4 items, 4 categories, an extras group and a 3-step combo (with a deposit drink). Replaces the menu on the target platform. Stores the first job id in `jobId`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"connectionIds\": [\n    \"{{connectionId}}\"\n  ],\n  \"currency\": \"EUR\",\n  \"products\": [\n    {\n      \"sku\": \"BURGER\",\n      \"name\": \"Cheeseburger\",\n      \"description\": \"Beef patty, cheddar, pickles, house sauce\",\n      \"priceMinor\": 890,\n      \"taxRatePercent\": 7,\n      \"imageUrl\": \"https://example.com/img/cheeseburger.jpg\",\n      \"modifierGroupIds\": [\n        \"G-EXTRAS\"\n      ],\n      \"allergens\": [\n        \"Gluten\",\n        \"Milk\",\n        \"Mustard\"\n      ]\n    },\n    {\n      \"sku\": \"FRIES\",\n      \"name\": \"Fries\",\n      \"priceMinor\": 350,\n      \"taxRatePercent\": 7,\n      \"foodInfo\": {\n        \"vegetarian\": true\n      }\n    },\n    {\n      \"sku\": \"COLA\",\n      \"name\": \"Cola 0.33 l\",\n      \"priceMinor\": 250,\n      \"taxRatePercent\": 19,\n      \"depositMinor\": 25,\n      \"depositTaxRatePercent\": 19,\n      \"foodInfo\": {\n        \"additives\": [\n          \"Mit Farbstoff(en)\",\n          \"Koffeinhaltig\"\n        ],\n        \"caffeineMgPer100ml\": 10,\n        \"netAmount\": 330,\n        \"netUnit\": \"ml\",\n        \"prepackaged\": true\n      }\n    },\n    {\n      \"sku\": \"MENU1\",\n      \"name\": \"Burger Menu\",\n      \"description\": \"Cheeseburger, fries and a drink\",\n      \"priceMinor\": 1190,\n      \"taxRatePercent\": 7,\n      \"modifierGroupIds\": [\n        \"STEP-MAIN\",\n        \"STEP-SIDE\",\n        \"STEP-DRINK\"\n      ]\n    }\n  ],\n  \"categories\": [\n    {\n      \"id\": \"cat-burgers\",\n      \"name\": \"Burgers\",\n      \"itemSkus\": [\n        \"BURGER\"\n      ]\n    },\n    {\n      \"id\": \"cat-sides\",\n      \"name\": \"Sides\",\n      \"itemSkus\": [\n        \"FRIES\"\n      ]\n    },\n    {\n      \"id\": \"cat-drinks\",\n      \"name\": \"Drinks\",\n      \"itemSkus\": [\n        \"COLA\"\n      ]\n    },\n    {\n      \"id\": \"cat-menus\",\n      \"name\": \"Menus\",\n      \"itemSkus\": [\n        \"MENU1\"\n      ]\n    }\n  ],\n  \"modifierGroups\": [\n    {\n      \"id\": \"G-EXTRAS\",\n      \"name\": \"Extras\",\n      \"minSelect\": 0,\n      \"maxSelect\": 3,\n      \"options\": [\n        {\n          \"sku\": \"X-CHEESE\",\n          \"name\": \"Extra cheese\",\n          \"priceMinor\": 100\n        },\n        {\n          \"sku\": \"X-ONION\",\n          \"name\": \"Onions\",\n          \"priceMinor\": 0\n        }\n      ]\n    },\n    {\n      \"id\": \"STEP-MAIN\",\n      \"name\": \"Main\",\n      \"minSelect\": 1,\n      \"maxSelect\": 1,\n      \"options\": [\n        {\n          \"sku\": \"BURGER\",\n          \"name\": \"Cheeseburger\",\n          \"priceMinor\": 0,\n          \"isDefault\": true\n        }\n      ]\n    },\n    {\n      \"id\": \"STEP-SIDE\",\n      \"name\": \"Side\",\n      \"minSelect\": 1,\n      \"maxSelect\": 1,\n      \"options\": [\n        {\n          \"sku\": \"FRIES\",\n          \"name\": \"Fries\",\n          \"priceMinor\": 0,\n          \"isDefault\": true\n        }\n      ]\n    },\n    {\n      \"id\": \"STEP-DRINK\",\n      \"name\": \"Drink\",\n      \"minSelect\": 1,\n      \"maxSelect\": 1,\n      \"options\": [\n        {\n          \"sku\": \"COLA\",\n          \"name\": \"Cola 0.33 l\",\n          \"priceMinor\": 0,\n          \"isDefault\": true,\n          \"depositMinor\": 25\n        }\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const jobs = pm.response.json().jobs || [];",
                  "jobs.forEach(j => pm.test(j.platform + ' ' + j.platformStoreId + ': ' + j.status + (j.lastError ? ' - ' + j.lastError : ''), () => pm.expect(j.status).to.not.equal('Failed')));",
                  "if (jobs[0]) pm.environment.set('jobId', jobs[0].jobId);"
                ]
              }
            }
          ]
        },
        {
          "name": "Update items (sold out / price)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/items",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "items"
              ]
            },
            "description": "Cola sold out, burger price 9.50, extra cheese sold out. On Wolt the update may be `Queued` (one item update per 15 min per venue).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"connectionIds\": [\n    \"{{connectionId}}\"\n  ],\n  \"items\": [\n    {\n      \"sku\": \"COLA\",\n      \"available\": false\n    },\n    {\n      \"sku\": \"BURGER\",\n      \"available\": true,\n      \"priceMinor\": 950\n    },\n    {\n      \"sku\": \"X-CHEESE\",\n      \"available\": false\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const jobs = pm.response.json().jobs || [];",
                  "jobs.forEach(j => pm.test(j.platform + ' ' + j.platformStoreId + ': ' + j.status + (j.lastError ? ' - ' + j.lastError : ''), () => pm.expect(j.status).to.not.equal('Failed')));",
                  "if (jobs[0]) pm.environment.set('jobId', jobs[0].jobId);"
                ]
              }
            }
          ]
        },
        {
          "name": "Job status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/jobs/{{jobId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "jobs",
                "{{jobId}}"
              ]
            },
            "description": "Status of a push job."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Store",
      "description": "Store status and opening hours of one connection (`connectionId`). Unsupported platforms answer 501 not_supported.",
      "item": [
        {
          "name": "Get store status",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/{{connectionId}}/store-status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "{{connectionId}}",
                "store-status"
              ]
            },
            "description": "isOpen / isOnline / openingTimes."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200 or 501', () => pm.expect(pm.response.code).to.be.oneOf([200, 501]));"
                ]
              }
            }
          ]
        },
        {
          "name": "Pause store until ...",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/{{connectionId}}/store-status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "{{connectionId}}",
                "store-status"
              ]
            },
            "description": "Pause until an ISO 8601 time. Check `status` in the response: 200 is returned even if the platform refused.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"open\": false,\n  \"closedUntil\": \"2026-12-31T18:00:00Z\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "pm.test('Job not failed', () => pm.expect(pm.response.json().status).to.not.equal('Failed'));"
                ]
              }
            }
          ]
        },
        {
          "name": "Open store",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/{{connectionId}}/store-status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "{{connectionId}}",
                "store-status"
              ]
            },
            "description": "Back online.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"open\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));",
                  "pm.test('Job not failed', () => pm.expect(pm.response.json().status).to.not.equal('Failed'));"
                ]
              }
            }
          ]
        },
        {
          "name": "Set opening hours",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/{{connectionId}}/opening-hours",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "{{connectionId}}",
                "opening-hours"
              ]
            },
            "description": "Replaces the whole week. Friday and Saturday are overnight windows (they close on the next day).",
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"openDay\": \"MONDAY\",\n    \"openTime\": \"11:00\",\n    \"closeDay\": \"MONDAY\",\n    \"closeTime\": \"22:00\"\n  },\n  {\n    \"openDay\": \"TUESDAY\",\n    \"openTime\": \"11:00\",\n    \"closeDay\": \"TUESDAY\",\n    \"closeTime\": \"22:00\"\n  },\n  {\n    \"openDay\": \"WEDNESDAY\",\n    \"openTime\": \"11:00\",\n    \"closeDay\": \"WEDNESDAY\",\n    \"closeTime\": \"22:00\"\n  },\n  {\n    \"openDay\": \"THURSDAY\",\n    \"openTime\": \"11:00\",\n    \"closeDay\": \"THURSDAY\",\n    \"closeTime\": \"22:00\"\n  },\n  {\n    \"openDay\": \"FRIDAY\",\n    \"openTime\": \"11:00\",\n    \"closeDay\": \"SATURDAY\",\n    \"closeTime\": \"02:00\"\n  },\n  {\n    \"openDay\": \"SATURDAY\",\n    \"openTime\": \"12:00\",\n    \"closeDay\": \"SUNDAY\",\n    \"closeTime\": \"02:00\"\n  },\n  {\n    \"openDay\": \"SUNDAY\",\n    \"openTime\": \"12:00\",\n    \"closeDay\": \"SUNDAY\",\n    \"closeTime\": \"21:30\"\n  }\n]",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 204 or 501', () => pm.expect(pm.response.code).to.be.oneOf([204, 501]));"
                ]
              }
            }
          ]
        },
        {
          "name": "Set opening hours (24/7)",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/{{connectionId}}/opening-hours",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "{{connectionId}}",
                "opening-hours"
              ]
            },
            "description": "Open around the clock: one 00:00 → 24:00 window per day.",
            "body": {
              "mode": "raw",
              "raw": "[\n  {\n    \"openDay\": \"MONDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"MONDAY\",\n    \"closeTime\": \"24:00\"\n  },\n  {\n    \"openDay\": \"TUESDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"TUESDAY\",\n    \"closeTime\": \"24:00\"\n  },\n  {\n    \"openDay\": \"WEDNESDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"WEDNESDAY\",\n    \"closeTime\": \"24:00\"\n  },\n  {\n    \"openDay\": \"THURSDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"THURSDAY\",\n    \"closeTime\": \"24:00\"\n  },\n  {\n    \"openDay\": \"FRIDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"FRIDAY\",\n    \"closeTime\": \"24:00\"\n  },\n  {\n    \"openDay\": \"SATURDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"SATURDAY\",\n    \"closeTime\": \"24:00\"\n  },\n  {\n    \"openDay\": \"SUNDAY\",\n    \"openTime\": \"00:00\",\n    \"closeDay\": \"SUNDAY\",\n    \"closeTime\": \"24:00\"\n  }\n]",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 204 or 501', () => pm.expect(pm.response.code).to.be.oneOf([204, 501]));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Sandbox flow (run in order)",
      "description": "End-to-end test against the built-in **Mock** platform. No real marketplace is involved.\n\n1. Create a Mock connection in the dashboard (**Sandbox → Create Mock connection**).\n2. Run requests 1–3.\n3. **Manual step:** in the dashboard open **Sandbox → Send test order** and click **Send**.\n4. Run requests 4–7.\n\n⚠ A paid test order on a fiscal POS creates a real fiscal record. Use a training / test till.",
      "item": [
        {
          "name": "1. Find the Mock connection",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/connections",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "connections"
              ]
            },
            "description": "Stores the active Mock connection in `connectionId`."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const mock = pm.response.json().find(c => c.platform === 'Mock' && c.isActive);",
                  "pm.test('A Mock (sandbox) connection exists - create one under Sandbox in the dashboard', () => pm.expect(mock).to.be.an('object'));",
                  "if (mock) pm.environment.set('connectionId', mock.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "2. Push sample menu to Mock",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/push",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "push"
              ]
            },
            "description": "The Mock platform validates the menu like a real platform; problems are listed in `lastError`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"connectionIds\": [\n    \"{{connectionId}}\"\n  ],\n  \"currency\": \"EUR\",\n  \"products\": [\n    {\n      \"sku\": \"BURGER\",\n      \"name\": \"Cheeseburger\",\n      \"description\": \"Beef patty, cheddar, pickles, house sauce\",\n      \"priceMinor\": 890,\n      \"taxRatePercent\": 7,\n      \"imageUrl\": \"https://example.com/img/cheeseburger.jpg\",\n      \"modifierGroupIds\": [\n        \"G-EXTRAS\"\n      ],\n      \"allergens\": [\n        \"Gluten\",\n        \"Milk\",\n        \"Mustard\"\n      ]\n    },\n    {\n      \"sku\": \"FRIES\",\n      \"name\": \"Fries\",\n      \"priceMinor\": 350,\n      \"taxRatePercent\": 7,\n      \"foodInfo\": {\n        \"vegetarian\": true\n      }\n    },\n    {\n      \"sku\": \"COLA\",\n      \"name\": \"Cola 0.33 l\",\n      \"priceMinor\": 250,\n      \"taxRatePercent\": 19,\n      \"depositMinor\": 25,\n      \"depositTaxRatePercent\": 19,\n      \"foodInfo\": {\n        \"additives\": [\n          \"Mit Farbstoff(en)\",\n          \"Koffeinhaltig\"\n        ],\n        \"caffeineMgPer100ml\": 10,\n        \"netAmount\": 330,\n        \"netUnit\": \"ml\",\n        \"prepackaged\": true\n      }\n    },\n    {\n      \"sku\": \"MENU1\",\n      \"name\": \"Burger Menu\",\n      \"description\": \"Cheeseburger, fries and a drink\",\n      \"priceMinor\": 1190,\n      \"taxRatePercent\": 7,\n      \"modifierGroupIds\": [\n        \"STEP-MAIN\",\n        \"STEP-SIDE\",\n        \"STEP-DRINK\"\n      ]\n    }\n  ],\n  \"categories\": [\n    {\n      \"id\": \"cat-burgers\",\n      \"name\": \"Burgers\",\n      \"itemSkus\": [\n        \"BURGER\"\n      ]\n    },\n    {\n      \"id\": \"cat-sides\",\n      \"name\": \"Sides\",\n      \"itemSkus\": [\n        \"FRIES\"\n      ]\n    },\n    {\n      \"id\": \"cat-drinks\",\n      \"name\": \"Drinks\",\n      \"itemSkus\": [\n        \"COLA\"\n      ]\n    },\n    {\n      \"id\": \"cat-menus\",\n      \"name\": \"Menus\",\n      \"itemSkus\": [\n        \"MENU1\"\n      ]\n    }\n  ],\n  \"modifierGroups\": [\n    {\n      \"id\": \"G-EXTRAS\",\n      \"name\": \"Extras\",\n      \"minSelect\": 0,\n      \"maxSelect\": 3,\n      \"options\": [\n        {\n          \"sku\": \"X-CHEESE\",\n          \"name\": \"Extra cheese\",\n          \"priceMinor\": 100\n        },\n        {\n          \"sku\": \"X-ONION\",\n          \"name\": \"Onions\",\n          \"priceMinor\": 0\n        }\n      ]\n    },\n    {\n      \"id\": \"STEP-MAIN\",\n      \"name\": \"Main\",\n      \"minSelect\": 1,\n      \"maxSelect\": 1,\n      \"options\": [\n        {\n          \"sku\": \"BURGER\",\n          \"name\": \"Cheeseburger\",\n          \"priceMinor\": 0,\n          \"isDefault\": true\n        }\n      ]\n    },\n    {\n      \"id\": \"STEP-SIDE\",\n      \"name\": \"Side\",\n      \"minSelect\": 1,\n      \"maxSelect\": 1,\n      \"options\": [\n        {\n          \"sku\": \"FRIES\",\n          \"name\": \"Fries\",\n          \"priceMinor\": 0,\n          \"isDefault\": true\n        }\n      ]\n    },\n    {\n      \"id\": \"STEP-DRINK\",\n      \"name\": \"Drink\",\n      \"minSelect\": 1,\n      \"maxSelect\": 1,\n      \"options\": [\n        {\n          \"sku\": \"COLA\",\n          \"name\": \"Cola 0.33 l\",\n          \"priceMinor\": 0,\n          \"isDefault\": true,\n          \"depositMinor\": 25\n        }\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const jobs = pm.response.json().jobs || [];",
                  "jobs.forEach(j => pm.test(j.platform + ' ' + j.platformStoreId + ': ' + j.status + (j.lastError ? ' - ' + j.lastError : ''), () => pm.expect(j.status).to.not.equal('Failed')));",
                  "if (jobs[0]) pm.environment.set('jobId', jobs[0].jobId);"
                ]
              }
            }
          ]
        },
        {
          "name": "3. Open the Mock store",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/catalog/{{connectionId}}/store-status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "catalog",
                "{{connectionId}}",
                "store-status"
              ]
            },
            "description": "Next: send a test order from the dashboard (Sandbox → Send test order).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"open\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.expect(pm.response.code).to.be.oneOf([200]));"
                ]
              }
            }
          ]
        },
        {
          "name": "4. Poll active orders (after sending a test order in the dashboard)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/active",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "active"
              ]
            },
            "description": "Manual step first: dashboard → Sandbox → Send test order → Send. In production your POS gets the order over SignalR (`OrderEvent`, kind `Received`); this is the REST fallback. Stores the Received Mock order in `orderId`."
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const orders = pm.response.code === 200 ? pm.response.json() : [];",
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "const next = orders.find(o => o.platform === 'Mock' && o.state === 'Received');",
                  "pm.test('A Received Mock order is waiting (send one from Sandbox > Send test order)', () => pm.expect(next).to.be.an('object'));",
                  "if (next) pm.environment.set('orderId', next.id);"
                ]
              }
            }
          ]
        },
        {
          "name": "5. Accept (20 min)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Accept the test order.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Accepted\",\n  \"prepTimeMinutes\": 20\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "6. Ready",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Mark the test order ready.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Ready\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        },
        {
          "name": "7. Delivered",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/orders/{{orderId}}/state",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "orders",
                "{{orderId}}",
                "state"
              ]
            },
            "description": "Complete the test order. It disappears from GET /pos/orders/active.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"state\": \"Delivered\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": [],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('HTTP 200', () => pm.response.to.have.status(200));",
                  "if (pm.response.code === 409) console.log('409:', pm.response.text(), '- refresh the order with GET /pos/orders/active');"
                ]
              }
            }
          ]
        }
      ]
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://hub.rapidpaygroup.com",
      "type": "string"
    }
  ]
}
