{
  "info": {
    "name": "Yuze Public API v1",
    "description": "Request names and descriptions match public-api/openapi.yaml in yuze-developer. Update both together.\n\nRead-only API. Authentication is a Bearer API key set at collection level from the `apiKey` environment variable.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "bearer",
    "bearer": [
      {
        "key": "token",
        "value": "{{apiKey}}",
        "type": "string"
      }
    ]
  },
  "event": [
    {
      "listen": "test",
      "script": {
        "type": "text/javascript",
        "exec": [
          "pm.test('responds with JSON and a request id', () => {",
          "  pm.response.to.have.header('X-Request-Id');",
          "  pm.expect(pm.response.headers.get('Content-Type')).to.include('application/json');",
          "});",
          "pm.test('never exposes PII, provider credentials or card details', () => {",
          "  pm.expect(pm.response.text()).to.not.match(new RegExp('stripeAcc|tidyPay|worldline|onlineFee|acct_TEST|holderName|MCFIXTURE|approvalCode|terminalId|providerTransactionCode|accountNumberMasked|\\\\*{4}|Testy|07700900000|example\\\\.test|\"customer\"|stripeId|pi_TEST|paymentIntentId|bankDetails|keyHash|_seed'));",
          "});"
        ]
      }
    }
  ],
  "variable": [
    {
      "key": "nextCursor",
      "value": ""
    },
    {
      "key": "page1Ids",
      "value": ""
    }
  ],
  "item": [
    {
      "name": "Meta",
      "item": [
        {
          "name": "Check service health",
          "request": {
            "method": "GET",
            "auth": {
              "type": "noauth"
            },
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "health"
              ]
            },
            "description": "Returns `ok` when the API is up. It needs no API key and no access type. It takes no parameters and is\nlimited to 30 calls a minute from one address.\n\n\nOperation: `getHealth`"
          },
          "response": [
            {
              "name": "200 Service is up",
              "code": 200,
              "status": "Service is up",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"data\": {\n    \"status\": \"ok\",\n    \"version\": \"1.0.0\",\n    \"time\": \"2026-11-02T10:00:00.000Z\"\n  },\n  \"meta\": {\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve your API key",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "me"
              ]
            },
            "description": "Returns the name, company, branches and access of the key you send. It needs no access type, so it is the\nquickest way to check a key works. It never returns the key itself.\n\n\nOperation: `getMe`"
          },
          "response": [
            {
              "name": "200 Key information",
              "code": 200,
              "status": "Key information",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"data\": {\n    \"keyId\": \"00000000000000a1\",\n    \"name\": \"Fixture key: full\",\n    \"environment\": \"test\",\n    \"companyId\": \"pubapiTestCoA\",\n    \"branchIds\": \"all\",\n    \"scopes\": [\n      \"company:read\",\n      \"branches:read\",\n      \"transactions:read\",\n      \"payments:read\",\n      \"reports:read\"\n    ],\n    \"createdAt\": \"2026-10-01T00:00:00.000Z\",\n    \"expiresAt\": null\n  },\n  \"meta\": {\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Company",
      "item": [
        {
          "name": "Retrieve your company",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/company",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            },
            "description": "Returns the company the key belongs to. Needs `company:read`. The company always comes from the key, so there\nis no company id to send.\n\n\nOperation: `getCompany`"
          },
          "response": [
            {
              "name": "200 Company",
              "code": 200,
              "status": "Company",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"data\": {\n    \"id\": \"pubapiTestCoA\",\n    \"name\": \"Fictional Pizza Co (TEST)\",\n    \"currency\": \"GBP\",\n    \"weekStartsOn\": 1,\n    \"timezone\": \"Europe/London\"\n  },\n  \"meta\": {\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "if (pm.environment.get('fixtureAssertions') === 'true') pm.test('fixture company', () => pm.expect(body.data.id).to.eql(pm.environment.get('companyId')));"
                ]
              }
            }
          ]
        },
        {
          "name": "List branches",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/branches",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "branches"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "contact",
                  "description": "`contact`. Adds the branch phone number and tax number. Left out: neither.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns the branches the key can read, sorted by name. Needs `branches:read`. Phone and tax number are left\nout unless you add `include=contact`.\n\n\nOperation: `listBranches`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('array of branches', () => pm.expect(body.data).to.be.an('array'));"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve a branch",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/branches/{{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "branches",
                "{{branchId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "contact",
                  "description": "`contact`. Adds the branch phone number and tax number. Left out: neither.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns one branch. Needs `branches:read`. A branch outside the key's branch list returns\n`403 branch_forbidden`, not `404`.\n\n\nOperation: `getBranch`"
          },
          "response": [
            {
              "name": "200 Branch",
              "code": 200,
              "status": "Branch",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"data\": {\n    \"id\": \"pubapiBranchA1\",\n    \"name\": \"Camden Test Kitchen\",\n    \"isActive\": true,\n    \"currency\": \"GBP\",\n    \"address\": {\n      \"line1\": \"1 Example Street\",\n      \"line2\": null,\n      \"town\": \"Testtown\",\n      \"county\": null,\n      \"postCode\": \"ZZ9 9ZZ\"\n    },\n    \"location\": \"Testtown\",\n    \"channels\": {\n      \"online\": true,\n      \"collection\": true,\n      \"delivery\": false,\n      \"kiosk\": false,\n      \"table\": true\n    }\n  },\n  \"meta\": {\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "List payment methods",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payment-methods?branchId={{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payment-methods"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                }
              ]
            },
            "description": "Returns the payment methods set up on the till, such as Cash and Card. Needs `branches:read`. Methods are\nsorted by branch, then in the order staff see them on the till.\n\n\nOperation: `listPaymentMethods`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Transactions",
      "item": [
        {
          "name": "List transactions",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?branchId={{branchId}}&from={{fromDate}}&to={{toDate}}&limit=50&expand=items,payments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{fromDate}}"
                },
                {
                  "key": "to",
                  "value": "{{toDate}}"
                },
                {
                  "key": "limit",
                  "value": "50"
                },
                {
                  "key": "expand",
                  "value": "items,payments"
                },
                {
                  "key": "platform",
                  "value": "",
                  "description": "One of `pos`, `online`, `kiosk`, `unknown`. Only transactions from that platform. Left out: every platform.",
                  "disabled": true
                },
                {
                  "key": "includeVoided",
                  "value": "",
                  "description": "Boolean. `true` adds voided records to the list. Left out: `false`.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "String. `pagination.nextCursor` from the previous page, sent with the same filters. Left out: the first page.",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "unit_cost,discount_code,card",
                  "description": "Comma-separated list of `unit_cost`, `discount_code`, `card`. Adds line item `unitCost`, `discount.code`, and the card provider and last four digits on payments (`card` needs `payments:read`). Left out: none of them.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns transactions, newest first, for a date range. Needs `transactions:read`; `amountPaid`, `tipAmount`,\n`paymentMethods` and `expand=payments` also need `payments:read`. Voided transactions are left out unless you\nadd `includeVoided=true`.\n\n\nOperation: `listTransactions`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('pagination block present', () => {",
                  "  pm.expect(body.pagination).to.include.keys('limit', 'hasMore', 'nextCursor');",
                  "});",
                  "pm.test('items are newest first', () => { const t = body.data.map(x => x.createdAt); pm.expect([...t].sort().reverse()).to.eql(t); });",
                  "if (body.pagination.nextCursor) pm.collectionVariables.set('nextCursor', body.pagination.nextCursor);"
                ]
              }
            }
          ]
        },
        {
          "name": "Retrieve a transaction",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions/{{transactionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions",
                "{{transactionId}}"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "unit_cost,discount_code,card",
                  "description": "Comma-separated list of `unit_cost`, `discount_code`, `card`. Adds line item `unitCost`, `discount.code`, and the card provider and last four digits on payments (`card` needs `payments:read`). Left out: none of them.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns one transaction with its line items, and its payments when the key also has `payments:read`. Needs\n`transactions:read`. A transaction in a branch the key cannot read returns `404`, as if it did not exist.\n\n\nOperation: `getTransaction`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('items belong to the transaction', () => body.data.items.forEach(i => pm.expect(i.transactionId).to.eql(body.data.id)));",
                  "if (pm.environment.get('fixtureAssertions') === 'true') pm.test('split payment fixture', () => pm.expect(body.data.payments.map(p => p.method)).to.eql(['Cash', 'Card']));"
                ]
              }
            }
          ]
        },
        {
          "name": "List a transaction's line items",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions/{{transactionId}}/items",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions",
                "{{transactionId}}",
                "items"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "unit_cost",
                  "description": "`unit_cost`. Adds `unitCost` to each line item. Left out: no unit cost.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns the line items of one transaction, oldest first. Needs `transactions:read`. A line is marked\n`isVoid: true` when the line or its whole transaction was voided.\n\n\nOperation: `listTransactionItems`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('items belong to the transaction', () => body.data.forEach(i => pm.expect(i.transactionId).to.eql(pm.environment.get('transactionId'))));"
                ]
              }
            }
          ]
        },
        {
          "name": "List a transaction's payments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions/{{transactionId}}/payments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions",
                "{{transactionId}}",
                "payments"
              ],
              "query": [
                {
                  "key": "include",
                  "value": "card",
                  "description": "`card`. Adds the card provider and last four digits to each payment. Left out: no card details.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns every payment taken against one transaction, oldest first, including voided and declined ones. Needs\n`payments:read`. A tip added after the bill was paid shows as its own payment with `amount` 0.\n\n\nOperation: `listTransactionPayments`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('payments belong to the transaction', () => body.data.forEach(p => pm.expect(p.transactionId).to.eql(pm.environment.get('transactionId'))));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Payments",
      "item": [
        {
          "name": "List payments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments?branchId={{branchId}}&from={{fromDate}}&to={{toDate}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{fromDate}}"
                },
                {
                  "key": "to",
                  "value": "{{toDate}}"
                },
                {
                  "key": "method",
                  "value": "Cash",
                  "description": "String. Only payments with this method name, case-insensitive, such as `Cash`, `Card` or `Online`. `Card` and `Credit Card` both return all card payments. Left out: every method.",
                  "disabled": true
                },
                {
                  "key": "includeVoided",
                  "value": "",
                  "description": "Boolean. `true` adds voided records to the list. Left out: `false`.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "description": "Integer, 1 to 200. Records per page. Left out: 50.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "String. `pagination.nextCursor` from the previous page, sent with the same filters. Left out: the first page.",
                  "disabled": true
                },
                {
                  "key": "include",
                  "value": "card",
                  "description": "`card`. Adds the card provider and last four digits to each payment. Left out: no card details.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns payments, newest first, for a date range. Needs `payments:read`. Voided and declined payments are left\nout unless you add `includeVoided=true`, and `Card` and `Credit Card` count as the same method.\n\n\nOperation: `listPayments`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('pagination block present', () => {",
                  "  pm.expect(body.pagination).to.include.keys('limit', 'hasMore', 'nextCursor');",
                  "});",
                  "pm.test('card data limited to provider and last4', () => body.data.filter(p => p.card).forEach(p => pm.expect(Object.keys(p.card).sort()).to.eql(['last4', 'provider'])));"
                ]
              }
            }
          ]
        },
        {
          "name": "Card payments (Card + Credit Card)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/payments?branchId={{branchId}}&from={{fromDate}}&to={{toDate}}&method=Card",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{fromDate}}"
                },
                {
                  "key": "to",
                  "value": "{{toDate}}"
                },
                {
                  "key": "method",
                  "value": "Card",
                  "description": "String. Only payments with this method name, case-insensitive, such as `Cash`, `Card` or `Online`. `Card` and `Credit Card` both return all card payments. Left out: every method."
                },
                {
                  "key": "include",
                  "value": "card",
                  "description": "`card`. Adds the card provider and last four digits to each payment. Left out: no card details.",
                  "disabled": true
                }
              ]
            },
            "description": "Lists card payments only. `method=Card` returns payments recorded as `Card` or `Credit Card`. Needs\n`payments:read`.\n\n\nOperation: `listPayments`"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('only card payments', () => body.data.forEach(p => pm.expect(['Card', 'Credit Card']).to.include(p.method)));",
                  "if (pm.environment.get('fixtureAssertions') === 'true') {",
                  "  pm.test('fixture: 4 card payments incl. Credit Card', () => {",
                  "    pm.expect(body.data).to.have.lengthOf(4);",
                  "    pm.expect(body.data.map(p => p.method)).to.include('Credit Card');",
                  "  });",
                  "}"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Reports",
      "item": [
        {
          "name": "Retrieve a sales summary",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reports/sales-summary?branchId={{branchId}}&from={{fromDate}}&to={{toDate}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reports",
                "sales-summary"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{fromDate}}"
                },
                {
                  "key": "to",
                  "value": "{{toDate}}"
                },
                {
                  "key": "groupBy",
                  "value": "",
                  "description": "`none` or `day`. `day` adds a breakdown per London day. Left out: `none`.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns gross and net sales, refunds, discounts, tips, tax and the splits by order type, platform and payment\ntype, worked out the same way as the Yuze dashboard Overview. Needs `reports:read`. Branches with different\ncurrencies cannot be added together, so send `branchId` for those or you get `422 mixed_currencies`.\n\n\nOperation: `getSalesSummary`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "if (pm.environment.get('fixtureAssertions') === 'true') pm.test('fixture figures match the Dashboard', () => {",
                  "  pm.expect(body.data.netSales).to.eql(139.4);",
                  "  pm.expect(body.data.grossSales).to.eql(154.6);",
                  "  pm.expect(body.data.tax).to.eql(20.1);",
                  "  pm.expect(body.data.transactionCount).to.eql(8);",
                  "});"
                ]
              }
            }
          ]
        },
        {
          "name": "List product sales",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reports/product-sales?branchId={{branchId}}&from={{fromDate}}&to={{toDate}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reports",
                "product-sales"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{fromDate}}"
                },
                {
                  "key": "to",
                  "value": "{{toDate}}"
                },
                {
                  "key": "groupBy",
                  "value": "",
                  "description": "`none` or `day`. `day` adds a breakdown per London day. Left out: `none`.",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "",
                  "description": "Integer, 1 to 500. Rows per page. Left out: 100.",
                  "disabled": true
                },
                {
                  "key": "cursor",
                  "value": "",
                  "description": "String. `pagination.nextCursor` from the previous page, sent with the same filters. Left out: the first page.",
                  "disabled": true
                }
              ]
            },
            "description": "Returns the quantity and value sold per product for a date range, like the dashboard product report. Needs\n`reports:read`. Voided and comped lines are counted in their own columns, not in `quantity`.\n\n\nOperation: `getProductSales`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "pm.test('pagination block present', () => {",
                  "  pm.expect(body.pagination).to.include.keys('limit', 'hasMore', 'nextCursor');",
                  "});",
                  "if (pm.environment.get('fixtureAssertions') === 'true') pm.test('fixture quantity', () => pm.expect(body.data.totals.quantity).to.eql(27));"
                ]
              }
            }
          ]
        },
        {
          "name": "List takings by payment type",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/reports/payment-types?branchId={{branchId}}&from={{fromDate}}&to={{toDate}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reports",
                "payment-types"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{fromDate}}"
                },
                {
                  "key": "to",
                  "value": "{{toDate}}"
                }
              ]
            },
            "description": "Returns takings per payment type for a date range, matching the dashboard \"Sales by payment type\" card. Needs\n`reports:read`. Amounts are net of refunds, and payments are dated by when they were taken, not when the\ntransaction was opened.\n\n\nOperation: `getPaymentTypes`"
          },
          "response": [
            {
              "name": "400 Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "code": 400,
              "status": "Invalid parameters (`invalid_request`, `invalid_cursor`, `range_too_large`)",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"invalid_request\",\n    \"message\": \"One or more parameters are invalid\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": [\n      {\n        \"field\": \"limit\",\n        \"message\": \"Too big: expected number to be <=200\"\n      }\n    ]\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "401 Missing, invalid, expired or revoked API key",
              "code": 401,
              "status": "Missing, invalid, expired or revoked API key",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"missing_api_key\",\n    \"message\": \"Provide an API key in the Authorization header as: Bearer <key>\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\"\n  }\n}",
              "_postman_previewlanguage": "json"
            },
            {
              "name": "403 `insufficient_scope` or `branch_forbidden`",
              "code": 403,
              "status": "`insufficient_scope` or `branch_forbidden`",
              "header": [
                {
                  "key": "Content-Type",
                  "value": "application/json"
                }
              ],
              "body": "{\n  \"error\": {\n    \"code\": \"insufficient_scope\",\n    \"message\": \"This API key does not have the required scope: reports:read\",\n    \"requestId\": \"6f1c2a9e-1111-4c4b-9d8e-0a1b2c3d4e5f\",\n    \"details\": {\n      \"requiredScope\": \"reports:read\"\n    }\n  }\n}",
              "_postman_previewlanguage": "json"
            }
          ],
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const body = pm.response.json();",
                  "pm.test('envelope has data and meta.requestId', () => {",
                  "  pm.expect(body).to.have.property('data');",
                  "  pm.expect(body.meta.requestId).to.be.a('string');",
                  "});",
                  "if (pm.environment.get('fixtureAssertions') === 'true') pm.test('fixture takings', () => pm.expect(body.data.totals.amount).to.eql(137.4));"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Pagination flow",
      "item": [
        {
          "name": "Page 1 (limit 5)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?branchId={{branchId}}&from={{paginationDate}}&to={{paginationDate}}&limit=5",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{paginationDate}}"
                },
                {
                  "key": "to",
                  "value": "{{paginationDate}}"
                },
                {
                  "key": "limit",
                  "value": "5"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const b = pm.response.json();",
                  "pm.test('has next cursor', () => pm.expect(b.pagination.hasMore).to.eql(true));",
                  "pm.collectionVariables.set('nextCursor', b.pagination.nextCursor);",
                  "pm.collectionVariables.set('page1Ids', JSON.stringify(b.data.map(t => t.id)));"
                ]
              }
            }
          ]
        },
        {
          "name": "Page 2 (uses nextCursor)",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?branchId={{branchId}}&from={{paginationDate}}&to={{paginationDate}}&limit=5&cursor={{nextCursor}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{branchId}}"
                },
                {
                  "key": "from",
                  "value": "{{paginationDate}}"
                },
                {
                  "key": "to",
                  "value": "{{paginationDate}}"
                },
                {
                  "key": "limit",
                  "value": "5"
                },
                {
                  "key": "cursor",
                  "value": "{{nextCursor}}"
                }
              ]
            }
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 200', () => pm.response.to.have.status(200));",
                  "const first = JSON.parse(pm.collectionVariables.get('page1Ids') || '[]');",
                  "pm.test('no overlap with page 1', () => pm.response.json().data.forEach(t => pm.expect(first).to.not.include(t.id)));"
                ]
              }
            }
          ]
        },
        {
          "name": "Cursor reused with another key",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{companyBKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?from={{paginationDate}}&to={{paginationDate}}&limit=5&cursor={{nextCursor}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "{{paginationDate}}"
                },
                {
                  "key": "to",
                  "value": "{{paginationDate}}"
                },
                {
                  "key": "limit",
                  "value": "5"
                },
                {
                  "key": "cursor",
                  "value": "{{nextCursor}}"
                }
              ]
            },
            "description": "Expected: 400 invalid_cursor"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 400', () => pm.response.to.have.status(400));",
                  "pm.test('error code invalid_cursor', () => pm.expect(pm.response.json().error.code).to.eql('invalid_cursor'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        }
      ]
    },
    {
      "name": "Negative tests",
      "item": [
        {
          "name": "Missing API key",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "noauth"
            },
            "url": {
              "raw": "{{baseUrl}}/v1/company",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            },
            "description": "Expected: 401 missing_api_key"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 401', () => pm.response.to.have.status(401));",
                  "pm.test('error code missing_api_key', () => pm.expect(pm.response.json().error.code).to.eql('missing_api_key'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Invalid API key",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{invalidKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/company",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            },
            "description": "Expected: 401 invalid_api_key"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 401', () => pm.response.to.have.status(401));",
                  "pm.test('error code invalid_api_key', () => pm.expect(pm.response.json().error.code).to.eql('invalid_api_key'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Expired API key",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{expiredKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/company",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            },
            "description": "Expected: 401 expired_api_key"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 401', () => pm.response.to.have.status(401));",
                  "pm.test('error code expired_api_key', () => pm.expect(pm.response.json().error.code).to.eql('expired_api_key'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Revoked API key",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{revokedKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/company",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "company"
              ]
            },
            "description": "Expected: 401 revoked_api_key"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 401', () => pm.response.to.have.status(401));",
                  "pm.test('error code revoked_api_key', () => pm.expect(pm.response.json().error.code).to.eql('revoked_api_key'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Insufficient scope (payments)",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{noReportsKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/payments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "payments"
              ]
            },
            "description": "Expected: 403 insufficient_scope"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 403', () => pm.response.to.have.status(403));",
                  "pm.test('error code insufficient_scope', () => pm.expect(pm.response.json().error.code).to.eql('insufficient_scope'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Insufficient scope (reports)",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{noReportsKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/reports/sales-summary",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "reports",
                "sales-summary"
              ]
            },
            "description": "Expected: 403 insufficient_scope"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 403', () => pm.response.to.have.status(403));",
                  "pm.test('error code insufficient_scope', () => pm.expect(pm.response.json().error.code).to.eql('insufficient_scope'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Unauthorised branch",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{branchRestrictedKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?branchId={{otherBranchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "{{otherBranchId}}"
                }
              ]
            },
            "description": "Expected: 403 branch_forbidden"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 403', () => pm.response.to.have.status(403));",
                  "pm.test('error code branch_forbidden', () => pm.expect(pm.response.json().error.code).to.eql('branch_forbidden'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Unauthorised branch by id",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{branchRestrictedKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/branches/{{otherBranchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "branches",
                "{{otherBranchId}}"
              ]
            },
            "description": "Expected: 403 branch_forbidden"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 403', () => pm.response.to.have.status(403));",
                  "pm.test('error code branch_forbidden', () => pm.expect(pm.response.json().error.code).to.eql('branch_forbidden'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Unknown branch filter",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?branchId=pubapiBranchZZ",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "pubapiBranchZZ"
                }
              ]
            },
            "description": "Expected: 404 not_found (same response as a branch of another company)"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 404', () => pm.response.to.have.status(404));",
                  "pm.test('error code not_found', () => pm.expect(pm.response.json().error.code).to.eql('not_found'));",
                  "pm.test('no branch details echoed', () => pm.expect(pm.response.json().error).to.not.have.property('details'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Other-company branch filter",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?branchId=pubapiBranchB1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "branchId",
                  "value": "pubapiBranchB1"
                }
              ]
            },
            "description": "Expected: 404 not_found (identical to an unknown branch, so branch ids of other companies cannot be discovered)"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 404', () => pm.response.to.have.status(404));",
                  "pm.test('error code not_found', () => pm.expect(pm.response.json().error.code).to.eql('not_found'));",
                  "pm.test('no branch details echoed', () => pm.expect(pm.response.json().error).to.not.have.property('details'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Other-branch transaction hidden",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{branchRestrictedKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions/{{otherBranchTransactionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions",
                "{{otherBranchTransactionId}}"
              ]
            },
            "description": "Expected: 404 not_found"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 404', () => pm.response.to.have.status(404));",
                  "pm.test('error code not_found', () => pm.expect(pm.response.json().error.code).to.eql('not_found'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Cross-company transaction hidden",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions/{{otherCompanyTransactionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions",
                "{{otherCompanyTransactionId}}"
              ]
            },
            "description": "Expected: 404 not_found"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 404', () => pm.response.to.have.status(404));",
                  "pm.test('error code not_found', () => pm.expect(pm.response.json().error.code).to.eql('not_found'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Tampered cursor",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?cursor=eyJ2IjoxfQ.forged",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "eyJ2IjoxfQ.forged"
                }
              ]
            },
            "description": "Expected: 400 invalid_cursor"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 400', () => pm.response.to.have.status(400));",
                  "pm.test('error code invalid_cursor', () => pm.expect(pm.response.json().error.code).to.eql('invalid_cursor'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Invalid date",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?from=2026-02-30",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-02-30"
                }
              ]
            },
            "description": "Expected: 400 invalid_request"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 400', () => pm.response.to.have.status(400));",
                  "pm.test('error code invalid_request', () => pm.expect(pm.response.json().error.code).to.eql('invalid_request'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Date range too large",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?from=2025-01-01&to=2026-10-14",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2025-01-01"
                },
                {
                  "key": "to",
                  "value": "2026-10-14"
                }
              ]
            },
            "description": "Expected: 400 range_too_large"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 400', () => pm.response.to.have.status(400));",
                  "pm.test('error code range_too_large', () => pm.expect(pm.response.json().error.code).to.eql('range_too_large'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Limit out of range",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?limit=500",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "500"
                }
              ]
            },
            "description": "Expected: 400 invalid_request"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 400', () => pm.response.to.have.status(400));",
                  "pm.test('error code invalid_request', () => pm.expect(pm.response.json().error.code).to.eql('invalid_request'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Unknown parameter",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions?companyId={{otherCompanyId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ],
              "query": [
                {
                  "key": "companyId",
                  "value": "{{otherCompanyId}}"
                }
              ]
            },
            "description": "Expected: 400 invalid_request"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 400', () => pm.response.to.have.status(400));",
                  "pm.test('error code invalid_request', () => pm.expect(pm.response.json().error.code).to.eql('invalid_request'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Unknown endpoint",
          "request": {
            "method": "GET",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "customers"
              ]
            },
            "description": "Expected: 404 not_found"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 404', () => pm.response.to.have.status(404));",
                  "pm.test('error code not_found', () => pm.expect(pm.response.json().error.code).to.eql('not_found'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        },
        {
          "name": "Write attempt rejected",
          "request": {
            "method": "POST",
            "header": [],
            "auth": {
              "type": "bearer",
              "bearer": [
                {
                  "key": "token",
                  "value": "{{apiKey}}",
                  "type": "string"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/v1/transactions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "v1",
                "transactions"
              ]
            },
            "description": "Expected: 405 method_not_allowed"
          },
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "pm.test('status 405', () => pm.response.to.have.status(405));",
                  "pm.test('error code method_not_allowed', () => pm.expect(pm.response.json().error.code).to.eql('method_not_allowed'));",
                  "pm.test('error has message and requestId', () => { const e = pm.response.json().error; pm.expect(e.message).to.be.a('string'); pm.expect(e.requestId).to.be.a('string'); });"
                ]
              }
            }
          ]
        }
      ]
    }
  ]
}