{
  "info": {
    "name": "OSA Admin Dashboard API",
    "description": "Executable contract for the OSA multi-tenant admin dashboard API. Must stay in sync with api/routes/api.php and admin/src/app/core/services/api/endpoints.ts.\n\nVariables:\n- baseUrl: API base incl. /api\n- tenant: X-Tenant slug for tenant-portal calls\n- accessToken / refreshToken: tenant user JWT (set by Login)\n- adminToken: super-admin JWT (set by Admin Login)\n- tenantId / roleId / userId: ids for path params",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "http://localhost:8000/api"
    },
    {
      "key": "tenant",
      "value": "acme"
    },
    {
      "key": "accessToken",
      "value": ""
    },
    {
      "key": "refreshToken",
      "value": ""
    },
    {
      "key": "adminToken",
      "value": ""
    },
    {
      "key": "tenantId",
      "value": "1"
    },
    {
      "key": "roleId",
      "value": "1"
    },
    {
      "key": "userId",
      "value": "1"
    },
    {
      "key": "verifyEmailToken",
      "value": ""
    },
    {
      "key": "notificationId",
      "value": ""
    },
    {
      "key": "attachmentId",
      "value": ""
    },
    {
      "key": "attachmentToken",
      "value": ""
    },
    {
      "key": "branchId",
      "value": "1"
    },
    {
      "key": "stockLocationId",
      "value": "1"
    },
    {
      "key": "counterId",
      "value": "1"
    },
    {
      "key": "employeeId",
      "value": "1"
    },
    {
      "key": "uomId",
      "value": "1"
    },
    {
      "key": "taxClassId",
      "value": "1"
    },
    {
      "key": "taxRateId",
      "value": "1"
    },
    {
      "key": "exchangeRateId",
      "value": "1"
    },
    {
      "key": "currencyCode",
      "value": "USD"
    },
    {
      "key": "categoryId",
      "value": "1"
    },
    {
      "key": "brandId",
      "value": "1"
    },
    {
      "key": "productId",
      "value": "1"
    },
    {
      "key": "variantId",
      "value": "1"
    },
    {
      "key": "bomHeaderId",
      "value": "1"
    },
    {
      "key": "warrantyTemplateId",
      "value": "1"
    },
    {
      "key": "serialId",
      "value": "1"
    },
    {
      "key": "barcodeId",
      "value": "1"
    },
    {
      "key": "labelTemplateId",
      "value": "1"
    },
    {
      "key": "labelJobId",
      "value": "1"
    },
    {
      "key": "importBatchId",
      "value": "1"
    },
    {
      "key": "importType",
      "value": "categories"
    },
    {
      "key": "batchId",
      "value": "1"
    },
    {
      "key": "reservationId",
      "value": "1"
    },
    {
      "key": "supplierId",
      "value": "1"
    },
    {
      "key": "purchaseOrderId",
      "value": "1"
    },
    {
      "key": "productionOrderId",
      "value": "1"
    },
    {
      "key": "stockRequestId",
      "value": "1"
    },
    {
      "key": "stockTransferId",
      "value": "1"
    },
    {
      "key": "stockAdjustmentId",
      "value": "1"
    },
    {
      "key": "stockCountId",
      "value": "1"
    },
    {
      "key": "goodsReceiptId",
      "value": "1"
    },
    {
      "key": "landedCostId",
      "value": "1"
    },
    {
      "key": "supplierInvoiceId",
      "value": "1"
    },
    {
      "key": "supplierReturnId",
      "value": "1"
    },
    {
      "key": "supplierPaymentId",
      "value": "1"
    }
  ],
  "item": [
    {
      "name": "Health",
      "item": [
        {
          "name": "Health check",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/health",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "health"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Tenant Auth",
      "item": [
        {
          "name": "Login",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.token) {",
                  "  pm.collectionVariables.set('accessToken', res.data.token);",
                  "}",
                  "pm.test('login ok', () => pm.response.code === 200);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"admin@acme.test\",\n  \"password\": \"password\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "login"
              ]
            }
          }
        },
        {
          "name": "Register",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"New User\",\n  \"email\": \"new@acme.test\",\n  \"password\": \"Password123\",\n  \"password_confirmation\": \"Password123\",\n  \"mobile\": \"0400000000\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/register",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "register"
              ]
            }
          }
        },
        {
          "name": "Me",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "me"
              ]
            }
          }
        },
        {
          "name": "Get Profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "profile"
              ]
            }
          }
        },
        {
          "name": "Update Profile",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/profile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "profile"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"full_name\": \"Acme Admin\",\n  \"contact_no\": \"+94 77 000 0000\",\n  \"user_name\": \"acme.admin\"\n}"
            }
          }
        },
        {
          "name": "Refresh",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.token) { pm.collectionVariables.set('accessToken', res.data.token); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/refresh",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "refresh"
              ]
            }
          }
        },
        {
          "name": "Change Password",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"new_password\": \"NewPass123\",\n  \"new_password_confirmation\": \"NewPass123\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/change-password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "change-password"
              ]
            }
          }
        },
        {
          "name": "Update Theme",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"theme\": {\n    \"LAYOUT_MODE\": \"dark\",\n    \"DATA_LAYOUT\": \"vertical\",\n    \"LAYOUT_WIDTH\": \"fluid\",\n    \"SIDEBAR_MODE\": \"brand\",\n    \"TOPBAR_TYPE\": \"brand\",\n    \"LAYOUT_POSITION\": false,\n    \"SIDEBAR_SIZE\": \"lg\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/theme",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "theme"
              ]
            }
          }
        },
        {
          "name": "Forgot Password",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"admin@acme.test\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/forgot-password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "forgot-password"
              ]
            }
          }
        },
        {
          "name": "Reset Password",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"RESET_TOKEN\",\n  \"email\": \"admin@acme.test\",\n  \"password\": \"NewPass123!\",\n  \"password_confirmation\": \"NewPass123!\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/reset-password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "reset-password"
              ]
            }
          }
        },
        {
          "name": "Get Invitation",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/invitation?token=INVITE_TOKEN&email=invitee@acme.test",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "invitation"
              ],
              "query": [
                {
                  "key": "token",
                  "value": "INVITE_TOKEN"
                },
                {
                  "key": "email",
                  "value": "invitee@acme.test"
                }
              ]
            }
          }
        },
        {
          "name": "Accept Invitation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"INVITE_TOKEN\",\n  \"email\": \"invitee@acme.test\",\n  \"password\": \"Str0ng!Pass\",\n  \"password_confirmation\": \"Str0ng!Pass\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/accept-invitation",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "accept-invitation"
              ]
            }
          }
        },
        {
          "name": "2FA Status",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/2fa/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "2fa",
                "status"
              ]
            }
          }
        },
        {
          "name": "2FA Enable",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/2fa/enable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "2fa",
                "enable"
              ]
            }
          }
        },
        {
          "name": "2FA Confirm",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"123456\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/2fa/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "2fa",
                "confirm"
              ]
            }
          }
        },
        {
          "name": "2FA Disable",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"123456\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/2fa/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "2fa",
                "disable"
              ]
            }
          }
        },
        {
          "name": "2FA Verify (login challenge)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"mfa_token\": \"MFA_TOKEN_FROM_LOGIN\",\n  \"code\": \"123456\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/auth/2fa/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "2fa",
                "verify"
              ]
            }
          }
        },
        {
          "name": "List Sessions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/sessions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "sessions"
              ]
            }
          }
        },
        {
          "name": "Revoke Other Sessions",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/sessions/revoke-others",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "sessions",
                "revoke-others"
              ]
            }
          }
        },
        {
          "name": "Revoke Session",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/sessions/{{sessionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "sessions",
                "{{sessionId}}"
              ]
            }
          }
        },
        {
          "name": "Logout",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/logout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "logout"
              ]
            }
          }
        },
        {
          "name": "Verify email",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/verify-email",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "verify-email"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{verifyEmailToken}}\"\n}"
            },
            "description": "Public — the encrypted token from the emailed link proves identity."
          }
        },
        {
          "name": "Resend verification",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/resend-verification",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "resend-verification"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{}"
            }
          }
        },
        {
          "name": "Upload avatar",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/avatar",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "avatar"
              ]
            },
            "description": "multipart/form-data: field `avatar` (image, max 2MB), or `remove_avatar=1` to clear."
          }
        },
        {
          "name": "List notifications",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/notifications?filter=all&per_page=15",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "notifications"
              ],
              "query": [
                {
                  "key": "filter",
                  "value": "all",
                  "disabled": true
                },
                {
                  "key": "per_page",
                  "value": "15",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Unread notification count",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/notifications/unread-count",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "notifications",
                "unread-count"
              ]
            }
          }
        },
        {
          "name": "Mark notification read",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/notifications/{{notificationId}}/read",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "notifications",
                "{{notificationId}}",
                "read"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{}"
            }
          }
        },
        {
          "name": "Mark all notifications read",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/notifications/read-all",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "notifications",
                "read-all"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{}"
            }
          }
        },
        {
          "name": "Delete notification",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/notifications/{{notificationId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "notifications",
                "{{notificationId}}"
              ]
            }
          }
        },
        {
          "name": "Clear notifications",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/notifications",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "notifications"
              ]
            }
          }
        },
        {
          "name": "Branches (switchable)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/branches",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "branches"
              ]
            }
          }
        },
        {
          "name": "Switch Branch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/auth/branch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "auth",
                "branch"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": \"{{branchId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Admin - Auth",
      "item": [
        {
          "name": "Admin Login",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.token) { pm.collectionVariables.set('adminToken', res.data.token); }",
                  "pm.test('admin login ok', () => pm.response.code === 200);"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"admin@example.com\",\n  \"password\": \"password\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/auth/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "auth",
                "login"
              ]
            }
          }
        },
        {
          "name": "Admin Me",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/auth/me",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "auth",
                "me"
              ]
            }
          }
        },
        {
          "name": "Admin Update Theme",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"theme\": {\n    \"LAYOUT_MODE\": \"dark\",\n    \"DATA_LAYOUT\": \"vertical\",\n    \"LAYOUT_WIDTH\": \"fluid\",\n    \"SIDEBAR_MODE\": \"brand\",\n    \"TOPBAR_TYPE\": \"brand\",\n    \"LAYOUT_POSITION\": false,\n    \"SIDEBAR_SIZE\": \"lg\"\n  }\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/auth/theme",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "auth",
                "theme"
              ]
            }
          }
        },
        {
          "name": "Admin Refresh",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/auth/refresh",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "auth",
                "refresh"
              ]
            }
          }
        },
        {
          "name": "Admin Logout",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/auth/logout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "auth",
                "logout"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Admin - Tenants",
      "item": [
        {
          "name": "List Tenants",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/tenants?per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "25"
                },
                {
                  "key": "search",
                  "value": "",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Create Tenant",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) { pm.collectionVariables.set('tenantId', res.data.id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Beta Inc\",\n  \"slug\": \"beta\",\n  \"db_driver\": \"sqlite\",\n  \"admin_name\": \"Beta Admin\",\n  \"admin_email\": \"admin@beta.test\",\n  \"admin_password\": \"password123\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants"
              ]
            }
          }
        },
        {
          "name": "Get Tenant",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}"
              ]
            }
          }
        },
        {
          "name": "Update Tenant",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Beta Incorporated\",\n  \"domain\": \"beta.example.com\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}"
              ]
            }
          }
        },
        {
          "name": "Set Status",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"suspended\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}",
                "status"
              ]
            }
          }
        },
        {
          "name": "Migrate Tenant",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}/migrate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}",
                "migrate"
              ]
            }
          }
        },
        {
          "name": "Tenant Users",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}",
                "users"
              ]
            }
          }
        },
        {
          "name": "Enter Organisation (full access)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}/impersonate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}",
                "impersonate"
              ]
            },
            "description": "Enter the organisation itself. With no user_id/email the session is granted every permission in the catalogue (imp_all claim), so the admin is not limited by whichever roles the underlying account holds. Still bound by the tenant's enabled modules."
          }
        },
        {
          "name": "Impersonate One User",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"user_id\": {{userId}}\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}/impersonate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}",
                "impersonate"
              ]
            },
            "description": "Sign in AS a specific tenant user, limited to exactly their permissions. Use this to reproduce one person's problem — not to administer the tenant."
          }
        },
        {
          "name": "Delete Tenant",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"drop_database\": true\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}"
              ]
            }
          }
        },
        {
          "name": "Admin activity logs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/activity-logs?per_page=25&event=&tenant_slug=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "activity-logs"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "25",
                  "disabled": true
                },
                {
                  "key": "event",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "tenant_slug",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Super-admin audit trail (central DB)."
          }
        },
        {
          "name": "Module Catalogue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/admin/modules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "modules"
              ]
            },
            "description": "Switchable feature areas. `core` entries are always on and cannot be disabled."
          }
        },
        {
          "name": "Set Tenant Modules",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{adminToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"modules\": [\n    \"users\",\n    \"roles\",\n    \"settings\"\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/admin/tenants/{{tenantId}}/modules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "admin",
                "tenants",
                "{{tenantId}}",
                "modules"
              ]
            },
            "description": "Send the COMPLETE enabled set, not a delta. Core modules are forced back on; unknown codes are rejected (422)."
          }
        }
      ]
    },
    {
      "name": "RBAC",
      "item": [
        {
          "name": "Catalogue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/catalogue",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "catalogue"
              ]
            }
          }
        },
        {
          "name": "List Roles",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/roles",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "roles"
              ]
            }
          }
        },
        {
          "name": "Create Role",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) { pm.collectionVariables.set('roleId', res.data.id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Editor\",\n  \"permissions\": [\"users.view\", \"settings.view\", \"settings.update\"]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/roles",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "roles"
              ]
            }
          }
        },
        {
          "name": "Update Role",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Senior Editor\",\n  \"permissions\": [\"users.view\", \"users.update\", \"settings.view\"]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/roles/{{roleId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "roles",
                "{{roleId}}"
              ]
            }
          }
        },
        {
          "name": "Delete Role",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/roles/{{roleId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "roles",
                "{{roleId}}"
              ]
            }
          }
        },
        {
          "name": "List Users",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users"
              ]
            }
          }
        },
        {
          "name": "Assign Roles",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"roles\": [{{roleId}}]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}/roles",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}",
                "roles"
              ]
            }
          }
        },
        {
          "name": "Create User",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) { pm.collectionVariables.set('userId', res.data.id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"full_name\": \"New Member\",\n  \"email\": \"member@acme.test\",\n  \"password\": \"Password123\",\n  \"roles\": []\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/users",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users"
              ]
            }
          }
        },
        {
          "name": "Get User",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}"
              ]
            }
          }
        },
        {
          "name": "Update User",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"full_name\": \"Renamed Member\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}"
              ]
            }
          }
        },
        {
          "name": "Set User Status",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": 0\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}/status",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}",
                "status"
              ]
            }
          }
        },
        {
          "name": "Get User Permissions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}/permissions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}",
                "permissions"
              ]
            }
          }
        },
        {
          "name": "Set User Permissions",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"overrides\": [\n    { \"code\": \"users.view\", \"granted\": true },\n    { \"code\": \"users.delete\", \"granted\": false }\n  ]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}/permissions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}",
                "permissions"
              ]
            }
          }
        },
        {
          "name": "Delete User",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}"
              ]
            }
          }
        },
        {
          "name": "Invite User",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) { pm.collectionVariables.set('invitationId', res.data.id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"full_name\": \"Invited User\",\n  \"email\": \"invitee@acme.test\",\n  \"role_id\": {{roleId}}\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/rbac/users/invite",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "invite"
              ]
            }
          }
        },
        {
          "name": "List Invitations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/invitations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "invitations"
              ]
            }
          }
        },
        {
          "name": "Resend Invitation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/invitations/{{invitationId}}/resend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "invitations",
                "{{invitationId}}",
                "resend"
              ]
            }
          }
        },
        {
          "name": "Revoke Invitation",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/invitations/{{invitationId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "invitations",
                "{{invitationId}}"
              ]
            }
          }
        },
        {
          "name": "Activity & Audit Logs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/activity-logs?log_type=audit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "activity-logs"
              ],
              "query": [
                {
                  "key": "log_type",
                  "value": "audit"
                }
              ]
            }
          }
        },
        {
          "name": "Export users (CSV)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/export?search=&status=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "export"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "status",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "Streams text/csv for the current filter set (permission: users.export)."
          }
        },
        {
          "name": "List deleted users",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/trashed?search=&per_page=10",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "trashed"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "per_page",
                  "value": "10",
                  "disabled": true
                }
              ]
            }
          }
        },
        {
          "name": "Restore user",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}/restore",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}",
                "restore"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{}"
            }
          }
        },
        {
          "name": "Permanently delete user",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/{{userId}}/force",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "{{userId}}",
                "force"
              ]
            },
            "description": "Frees the email address; a soft-deleted user still holds it in the unique index."
          }
        },
        {
          "name": "Bulk user action",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/users/bulk",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "users",
                "bulk"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"deactivate\",\n  \"ids\": [\n    2,\n    3\n  ]\n}"
            },
            "description": "action: activate | deactivate | delete. The caller's own id is always skipped."
          }
        },
        {
          "name": "Export activity logs (CSV)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/rbac/activity-logs/export?log_type=audit&from=&to=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "rbac",
                "activity-logs",
                "export"
              ],
              "query": [
                {
                  "key": "log_type",
                  "value": "audit"
                },
                {
                  "key": "from",
                  "value": "",
                  "disabled": true
                },
                {
                  "key": "to",
                  "value": "",
                  "disabled": true
                }
              ]
            },
            "description": "permission: logs.export"
          }
        }
      ]
    },
    {
      "name": "Dashboard",
      "item": [
        {
          "name": "Dashboard stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/dashboard/stats",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "dashboard",
                "stats"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Attachments",
      "item": [
        {
          "name": "List attachments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments?attachable_type=user&attachable_id=1&collection=default&unattached=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments"
              ],
              "query": [
                {
                  "key": "attachable_type",
                  "value": "user",
                  "disabled": true
                },
                {
                  "key": "attachable_id",
                  "value": "1",
                  "disabled": true
                },
                {
                  "key": "collection",
                  "value": "default",
                  "disabled": true
                },
                {
                  "key": "unattached",
                  "value": "1",
                  "disabled": true
                }
              ]
            },
            "description": "Filter by owning record, or pass unattached=1 for draft uploads."
          }
        },
        {
          "name": "Upload files",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments"
              ]
            },
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "files[]",
                  "type": "file",
                  "src": []
                },
                {
                  "key": "attachable_type",
                  "value": "user",
                  "type": "text",
                  "disabled": true
                },
                {
                  "key": "attachable_id",
                  "value": "1",
                  "type": "text",
                  "disabled": true
                },
                {
                  "key": "collection",
                  "value": "default",
                  "type": "text",
                  "disabled": true
                }
              ]
            },
            "description": "multipart/form-data. Omit attachable_* to park the upload as a draft, then claim it with /attachments/attach. Type is checked on BOTH extension and detected MIME (config/attachments.php)."
          }
        },
        {
          "name": "Attach draft uploads",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments/attach",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments",
                "attach"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ids\": [\n    1\n  ],\n  \"attachable_type\": \"user\",\n  \"attachable_id\": 1,\n  \"collection\": \"default\"\n}"
            },
            "description": "Claims previously uploaded unattached files. Files already owned by a record are never re-assigned."
          }
        },
        {
          "name": "Show attachment",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments/{{attachmentId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments",
                "{{attachmentId}}"
              ]
            }
          }
        },
        {
          "name": "Download attachment",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments/{{attachmentId}}/download?disposition=inline",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments",
                "{{attachmentId}}",
                "download"
              ],
              "query": [
                {
                  "key": "disposition",
                  "value": "inline",
                  "disabled": true
                }
              ]
            },
            "description": "Streams the file to an authenticated caller. Always Content-Disposition: attachment unless the type is on the inline allowlist."
          }
        },
        {
          "name": "Create download link",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments/{{attachmentId}}/link",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments",
                "{{attachmentId}}",
                "link"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"disposition\": \"inline\"\n}"
            },
            "description": "Short-lived self-authorising URL for an <img>/<iframe> or a new tab."
          }
        },
        {
          "name": "Signed download (public)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments/download?token={{attachmentToken}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments",
                "download"
              ],
              "query": [
                {
                  "key": "token",
                  "value": "{{attachmentToken}}"
                }
              ]
            },
            "description": "No auth and no X-Tenant: the tenant is read from the token itself. This is the URL returned by /link."
          }
        },
        {
          "name": "Delete attachment",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/attachments/{{attachmentId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "attachments",
                "{{attachmentId}}"
              ]
            },
            "description": "Soft delete — the file survives until tenants:prune purges it."
          }
        }
      ]
    },
    {
      "name": "Settings",
      "item": [
        {
          "name": "Get System Settings",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/system",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "system"
              ]
            }
          }
        },
        {
          "name": "Update System Settings",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"app_name\": \"Acme Corp\",\n  \"timezone\": \"Asia/Colombo\",\n  \"date_format\": \"YYYY-MM-DD\",\n  \"mail_host\": \"smtp.mailgun.org\",\n  \"mail_port\": 587,\n  \"mail_encryption\": \"tls\",\n  \"enforce_2fa\": false,\n  \"session_timeout\": 30,\n  \"maintenance_mode\": false\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/system",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "system"
              ]
            }
          }
        },
        {
          "name": "Get Organization Profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/organization",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "organization"
              ]
            }
          }
        },
        {
          "name": "Update Organization Profile",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Acme Corp\",\n  \"legal_name\": \"Acme Corporation (Pvt) Ltd\",\n  \"email\": \"hello@acme.test\",\n  \"phone\": \"+94 11 234 5678\",\n  \"city\": \"Colombo\",\n  \"country\": \"Sri Lanka\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/organization",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "organization"
              ]
            },
            "description": "Send as multipart/form-data with a `logo` file (and `_method=PUT`) to upload a logo."
          }
        },
        {
          "name": "List Pick Lists",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists"
              ]
            }
          }
        },
        {
          "name": "Create Pick List",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) { pm.collectionVariables.set('pickListId', res.data.id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Room Types\",\n  \"description\": \"Bookable room categories\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists"
              ]
            }
          }
        },
        {
          "name": "Update Pick List",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Room Categories\",\n  \"description\": \"Types of rooms\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists/{{pickListId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists",
                "{{pickListId}}"
              ]
            }
          }
        },
        {
          "name": "Add Option",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.options && res.data.options.length) { pm.collectionVariables.set('optionId', res.data.options[res.data.options.length - 1].id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label\": \"Double Room\",\n  \"value\": \"double\",\n  \"sort_order\": 1,\n  \"active\": true\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists/{{pickListId}}/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists",
                "{{pickListId}}",
                "options"
              ]
            }
          }
        },
        {
          "name": "Update Option",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label\": \"Double Deluxe\",\n  \"value\": \"double_deluxe\",\n  \"sort_order\": 2,\n  \"active\": false\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists/{{pickListId}}/options/{{optionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists",
                "{{pickListId}}",
                "options",
                "{{optionId}}"
              ]
            }
          }
        },
        {
          "name": "Delete Option",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists/{{pickListId}}/options/{{optionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists",
                "{{pickListId}}",
                "options",
                "{{optionId}}"
              ]
            }
          }
        },
        {
          "name": "Delete Pick List",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/pick-lists/{{pickListId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "pick-lists",
                "{{pickListId}}"
              ]
            }
          }
        },
        {
          "name": "List Media Folders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/media/folders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "media",
                "folders"
              ]
            }
          }
        },
        {
          "name": "Create Media Folder",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.data && res.data.id) { pm.collectionVariables.set('folderId', res.data.id); }"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Rooms\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/media/folders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "media",
                "folders"
              ]
            }
          }
        },
        {
          "name": "Upload Images",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "files[]",
                  "type": "file",
                  "src": []
                },
                {
                  "key": "folder_id",
                  "value": "{{folderId}}",
                  "type": "text"
                }
              ]
            },
            "url": {
              "raw": "{{baseUrl}}/settings/media",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "media"
              ]
            }
          }
        },
        {
          "name": "List Images",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/media?folder_id=&search=&from=&to=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "media"
              ],
              "query": [
                {
                  "key": "folder_id",
                  "value": ""
                },
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "from",
                  "value": "",
                  "description": "Filter images created on/after this date (YYYY-MM-DD)"
                },
                {
                  "key": "to",
                  "value": "",
                  "description": "Filter images created on/before this date (YYYY-MM-DD)"
                }
              ]
            }
          }
        },
        {
          "name": "Delete Images",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ids\": [1]\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/settings/media",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "media"
              ]
            }
          }
        },
        {
          "name": "Delete Media Folder",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/settings/media/folders/{{folderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "settings",
                "media",
                "folders",
                "{{folderId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Data Import (Screen P.5)",
      "item": [
        {
          "name": "List Import Types",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/types",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "types"
              ]
            },
            "description": "Each type describes the columns its file needs, which is what Screen P.5 renders on the \"Choose type\" step."
          }
        },
        {
          "name": "Download Import Template",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/types/{{importType}}/template",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "types",
                "{{importType}}",
                "template"
              ]
            },
            "description": "Returns text/csv with the exact header the parser expects, plus one example row."
          }
        },
        {
          "name": "List Import Batches",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports?type=&status=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports"
              ],
              "query": [
                {
                  "key": "type",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Upload & Validate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports"
              ]
            },
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "type",
                  "value": "categories",
                  "type": "text"
                },
                {
                  "key": "file",
                  "type": "file",
                  "src": []
                }
              ]
            },
            "description": "Uploads the file and validates it in one call. The response says whether it is `validated` (no errors) or `failed`. Nothing is written at this point."
          }
        },
        {
          "name": "Get Import Batch",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}"
              ]
            }
          }
        },
        {
          "name": "Re-validate Batch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}",
                "validate"
              ]
            },
            "description": "Re-checks the stored file. This WITHDRAWS any approval the batch had — the verdict that was approved is no longer the verdict that stands."
          }
        },
        {
          "name": "List Import Errors",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}/errors?field=&per_page=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}",
                "errors"
              ],
              "query": [
                {
                  "key": "field",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "50"
                }
              ]
            },
            "description": "Row numbers count the header as row 1, so they match the person’s spreadsheet. `meta.fields` carries the per-column counts behind the error-type filter."
          }
        },
        {
          "name": "Download Error Report",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}/errors/export",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}",
                "errors",
                "export"
              ]
            },
            "description": "The correct-and-re-upload loop: text/csv of every problem found."
          }
        },
        {
          "name": "Preview Batch",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}",
                "preview"
              ]
            },
            "description": "The first rows as they would land, in the importer’s terms rather than the file’s columns."
          }
        },
        {
          "name": "Approve Reconciliation",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}",
                "approve"
              ]
            },
            "description": "Requires `imports.approve`, deliberately separate from `imports.run`: E1 requires the reconciliation to be signed off, and an approval by whoever uploaded the file is not a control. A batch with errors cannot be approved."
          }
        },
        {
          "name": "Commit Batch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}/commit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}",
                "commit"
              ]
            },
            "description": "Re-validates first (the world may have moved since approval), then writes the whole batch in one transaction. Never partial: E1 requires an import to reconcile, which is impossible if rows were silently dropped. The uploaded file is deleted once committed."
          }
        },
        {
          "name": "Cancel Batch",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/imports/{{importBatchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "imports",
                "{{importBatchId}}"
              ]
            },
            "description": "Abandons the batch and deletes its file. A committed batch cannot be cancelled — its record is the audit trail for what was loaded."
          }
        }
      ]
    },
    {
      "name": "Organisation - Branches",
      "description": "M1 - branches, their stock locations and POS counters (Screens 1.1-1.3). Gated by the `branches` module and the `branches.*` permissions.",
      "item": [
        {
          "name": "List Branches",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/branches?search=&status=&per_page=10",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "branches"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "10"
                }
              ]
            }
          }
        },
        {
          "name": "Create Branch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/branches",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "branches"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"CMB\",\n  \"name\": \"Colombo Main\",\n  \"legal_name\": \"Acme Retail (Pvt) Ltd\",\n  \"address_line1\": \"112 Galle Road\",\n  \"city\": \"Colombo\",\n  \"postal_code\": \"00300\",\n  \"phone\": \"+94112345678\",\n  \"timezone\": \"Asia/Colombo\",\n  \"number_prefix\": \"CMB\",\n  \"allow_negative_stock\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Branch",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/branches/{{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "branches",
                "{{branchId}}"
              ]
            }
          }
        },
        {
          "name": "Update Branch",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/branches/{{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "branches",
                "{{branchId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Colombo Flagship\",\n  \"status\": \"active\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Branch",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/branches/{{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "branches",
                "{{branchId}}"
              ]
            }
          }
        },
        {
          "name": "List Stock Locations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-locations?branch_id={{branchId}}&type=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-locations"
              ],
              "query": [
                {
                  "key": "branch_id",
                  "value": "{{branchId}}"
                },
                {
                  "key": "type",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Create Stock Location",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-locations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-locations"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": \"{{branchId}}\",\n  \"code\": \"FLOOR\",\n  \"name\": \"Sales Floor\",\n  \"type\": \"sales_floor\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Stock Location",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-locations/{{stockLocationId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-locations",
                "{{stockLocationId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Main Sales Floor\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Stock Location",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-locations/{{stockLocationId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-locations",
                "{{stockLocationId}}"
              ]
            }
          }
        },
        {
          "name": "List Counters",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/counters?branch_id={{branchId}}&status=&search=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "counters"
              ],
              "query": [
                {
                  "key": "branch_id",
                  "value": "{{branchId}}"
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Create Counter",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/counters",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "counters"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": \"{{branchId}}\",\n  \"code\": \"C1\",\n  \"name\": \"Counter 1\",\n  \"device_id\": \"POS-CMB-01\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Counter",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/counters/{{counterId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "counters",
                "{{counterId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Counter 1 - Express\",\n  \"status\": \"online\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Counter",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/counters/{{counterId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "counters",
                "{{counterId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Organisation - Employees",
      "description": "M2 - the employee register (Screens 2.1-2.2). Gated by the `employees` module and `employees.*`. A branch move is a transfer, not an update: it records a new assignment period so the history stays truthful.",
      "item": [
        {
          "name": "List Employees",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/employees?search=&branch_id=&job_title=&status=&per_page=15",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "employees"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "branch_id",
                  "value": ""
                },
                {
                  "key": "job_title",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "15"
                }
              ]
            }
          }
        },
        {
          "name": "Create Employee",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/employees",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "employees"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": \"{{branchId}}\",\n  \"full_name\": \"N. Silva\",\n  \"national_id\": \"199012345678\",\n  \"date_of_birth\": \"1990-04-11\",\n  \"phone\": \"+94771234567\",\n  \"email\": \"n.silva@acme.test\",\n  \"address\": \"42 Marine Drive, Colombo\",\n  \"job_title\": \"Cashier\",\n  \"employment_type\": \"full_time\",\n  \"joined_on\": \"2026-01-15\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Employee",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/employees/{{employeeId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "employees",
                "{{employeeId}}"
              ]
            }
          }
        },
        {
          "name": "Update Employee",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/employees/{{employeeId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "employees",
                "{{employeeId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"job_title\": \"Senior Cashier\",\n  \"status\": \"active\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Transfer Employee",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/employees/{{employeeId}}/transfer",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "employees",
                "{{employeeId}}",
                "transfer"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": \"{{branchId}}\",\n  \"started_on\": \"2026-06-01\",\n  \"note\": \"Covering the new store\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Employee",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/employees/{{employeeId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "employees",
                "{{employeeId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Catalogue - Units of Measure",
      "description": "B12 / Screen 3.5. System units are seeded physical constants and are read-only; only packaging units can be written. Quantities and factors travel as STRINGS, never JSON numbers, because a JSON number is an IEEE double and would undo the exact arithmetic.",
      "item": [
        {
          "name": "List Units",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/uoms?family=&search=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "uoms"
              ],
              "query": [
                {
                  "key": "family",
                  "value": ""
                },
                {
                  "key": "search",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Convert Quantity",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/uoms/convert",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "uoms",
                "convert"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"quantity\": \"3\",\n  \"from\": \"ft\",\n  \"to\": \"in\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Create Packaging Unit",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/uoms",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "uoms"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"family\": \"count\",\n  \"code\": \"case24\",\n  \"name\": \"Case of 24\",\n  \"factor_to_base\": \"24.000000\",\n  \"decimals\": 0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Packaging Unit",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/uoms/{{uomId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "uoms",
                "{{uomId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Case (24)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Packaging Unit",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/uoms/{{uomId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "uoms",
                "{{uomId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Tax Configuration",
      "description": "B10 / Screen P.3. Tax is switched on per organisation via the `tax_enabled` system setting; when off, nothing here is consulted. Rates are effective-dated and are retired with `close`, never by editing the percentage — a posted document must still compute the tax it was issued with. Percentages and amounts travel as STRINGS.",
      "item": [
        {
          "name": "List Tax Classes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/classes?status=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "classes"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Preview Line Tax",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "preview"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"100.00\",\n  \"tax_class_id\": \"{{taxClassId}}\",\n  \"date\": \"2026-08-25\",\n  \"inclusive\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Create Tax Class",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/classes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "classes"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"STD\",\n  \"name\": \"Standard\",\n  \"kind\": \"standard\",\n  \"description\": \"Standard-rated supplies\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Tax Class",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/classes/{{taxClassId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "classes",
                "{{taxClassId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Standard rate\",\n  \"default_rate_id\": \"{{taxRateId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Tax Class",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/classes/{{taxClassId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "classes",
                "{{taxClassId}}"
              ]
            }
          }
        },
        {
          "name": "Create Tax Rate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/rates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "rates"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tax_class_id\": \"{{taxClassId}}\",\n  \"name\": \"VAT 15%\",\n  \"code\": \"VAT15\",\n  \"percentage\": \"15.0000\",\n  \"is_recoverable\": true,\n  \"effective_from\": \"2026-07-01\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Close Tax Rate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/rates/{{taxRateId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "rates",
                "{{taxRateId}}",
                "close"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"effective_to\": \"2026-12-31\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Tax Rate",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/rates/{{taxRateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "rates",
                "{{taxRateId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"VAT (standard)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Tax Rate",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/rates/{{taxRateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "rates",
                "{{taxRateId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Currencies & Exchange Rates",
      "description": "B11 / Screen P.4. The base currency is fixed when the tenant is provisioned and there is NO endpoint that changes it — every cost layer and posted document is denominated in it. Rates are entered manually and dated; there is no edit-rate route either, because a wrong rate is corrected by entering the right one for that date, which keeps the mistake visible. Rates and amounts travel as STRINGS.",
      "item": [
        {
          "name": "List Currencies",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/currencies",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "currencies"
              ]
            }
          }
        },
        {
          "name": "Convert Amount",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/currencies/convert",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "currencies",
                "convert"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"10\",\n  \"currency\": \"USD\",\n  \"date\": \"2026-08-25\",\n  \"rate_type\": \"sell\",\n  \"direction\": \"to_base\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Currency",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/currencies/{{currencyCode}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "currencies",
                "{{currencyCode}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"symbol\": \"US$\",\n  \"is_active\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Exchange Rates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/exchange-rates?currency=&rate_type=&from=&to=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "exchange-rates"
              ],
              "query": [
                {
                  "key": "currency",
                  "value": ""
                },
                {
                  "key": "rate_type",
                  "value": ""
                },
                {
                  "key": "from",
                  "value": ""
                },
                {
                  "key": "to",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Enter Exchange Rate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/exchange-rates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "exchange-rates"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"currency_code\": \"USD\",\n  \"rate_type\": \"sell\",\n  \"rate\": \"302.500000\",\n  \"effective_date\": \"2026-08-19\",\n  \"source\": \"Counter rate\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Exchange Rate",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/exchange-rates/{{exchangeRateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "exchange-rates",
                "{{exchangeRateId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Catalogue - Categories & Brands",
      "description": "Screen 3.4. Categories are EXACTLY three levels (Clothing > Men > Shirts) and the tree refuses a fourth — enforced on the model, so it also holds for the import framework. Re-parenting is refused when it would push descendants past level 3, or place a category inside its own subtree. Brands are managed separately.",
      "item": [
        {
          "name": "Get Category Tree",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/categories?status=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "categories"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Create Root Category",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "categories"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Clothing\",\n  \"parent_id\": null,\n  \"sort_order\": 0,\n  \"is_web_visible\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Create Sub-Category",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "categories"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Men\",\n  \"parent_id\": \"{{categoryId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Category",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/categories/{{categoryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "categories",
                "{{categoryId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Menswear\",\n  \"sort_order\": 1,\n  \"is_web_visible\": true,\n  \"tax_class_id\": \"{{taxClassId}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Category",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/categories/{{categoryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "categories",
                "{{categoryId}}"
              ]
            }
          }
        },
        {
          "name": "List Brands",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/brands?search=&status=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "brands"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Create Brand",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/brands",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "brands"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Acme Apparel\",\n  \"description\": \"House brand\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Brand",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/brands/{{brandId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "brands",
                "{{brandId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"description\": \"House brand (renamed)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Brand",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/brands/{{brandId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "brands",
                "{{brandId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Catalogue - Products & Variants",
      "description": "Screens 3.1-3.3. A product is what a merchandiser thinks about; a VARIANT is what is stocked, priced and sold — even an option-less product gets exactly one.\n\n`is_serial_tracked` and `costing_method` are LOCKED once stock exists: changing them would reinterpret the stock history rather than change future behaviour. `stock_uom_id` cannot be changed at all after creation, for the same reason.\n\nRegenerate keeps combinations that still exist, adds new ones, and DISCONTINUES ones that disappeared — it never deletes a SKU, because movements and documents still refer to it.\n\nMoney and quantities travel as STRINGS.",
      "item": [
        {
          "name": "List Products",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products?search=&category_id=&brand_id=&type=&status=&per_page=15",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "category_id",
                  "value": ""
                },
                {
                  "key": "brand_id",
                  "value": ""
                },
                {
                  "key": "type",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "15"
                }
              ]
            }
          }
        },
        {
          "name": "Create Product",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Cotton Shirt\",\n  \"stock_uom_id\": \"{{uomId}}\",\n  \"category_id\": \"{{categoryId}}\",\n  \"brand_id\": \"{{brandId}}\",\n  \"tax_class_id\": \"{{taxClassId}}\",\n  \"type\": \"single\",\n  \"is_serial_tracked\": false,\n  \"costing_method\": \"weighted_average\",\n  \"short_description\": \"Everyday cotton shirt\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Product",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}"
              ]
            }
          }
        },
        {
          "name": "Update Product",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Cotton Shirt (Classic)\",\n  \"status\": \"active\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Product",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}"
              ]
            }
          }
        },
        {
          "name": "Set Variant Attributes",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/attributes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "attributes"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"attributes\": [\n    {\n      \"name\": \"Size\",\n      \"values\": [\n        \"S\",\n        \"M\",\n        \"L\",\n        \"XL\"\n      ]\n    },\n    {\n      \"name\": \"Colour\",\n      \"values\": [\n        \"Navy\",\n        \"White\",\n        \"Black\"\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Generate Variants",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/variants/generate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "variants",
                "generate"
              ]
            }
          }
        },
        {
          "name": "Save Variants",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/variants",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "variants"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"variants\": [\n    {\n      \"id\": \"{{variantId}}\",\n      \"barcode\": \"5012345678900\",\n      \"cost\": \"1200.0000\",\n      \"retail_price\": \"1999.0000\",\n      \"wholesale_price\": \"1750.0000\",\n      \"reorder_point\": \"10\",\n      \"lifecycle_state\": \"active\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Catalogue - Combos & Recipes",
      "description": "T018 / Screen 3.6.\n\nA COMBO is a commercial bundle — nothing is produced, its components are consumed on sale. Unversioned: what a bundle contained on the day it sold is recorded on the sale itself.\n\nA MANUFACTURED product has a versioned, effective-dated RECIPE. An APPROVED version is never edited — production runs reference it — so `new-version` copies it into a fresh draft instead. Approving closes the previous version the day before, leaving no overlap and no gap.\n\nWASTAGE is per line (offcuts: more is drawn than ends up in the output). YIELD is per run (97% yield means one good unit needs materials for 1/0.97). They are applied at different levels.\n\nQuantities, percentages and money all travel as STRINGS.",
      "item": [
        {
          "name": "Get Combo Components",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/combo",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "combo"
              ]
            }
          }
        },
        {
          "name": "Save Combo Components",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/combo",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "combo"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"components\": [\n    {\n      \"component_variant_id\": \"{{variantId}}\",\n      \"quantity\": \"2\",\n      \"uom_id\": \"{{uomId}}\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Recipe Versions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/recipes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "recipes"
              ]
            }
          }
        },
        {
          "name": "Create Recipe Draft",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/products/{{productId}}/recipes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "products",
                "{{productId}}",
                "recipes"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"output_quantity\": \"1\",\n  \"yield_percent\": \"97\",\n  \"labour_cost\": \"180\",\n  \"overhead_cost\": \"0\",\n  \"effective_from\": \"2026-07-01\",\n  \"notes\": \"Standard make\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Recipe Draft",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/recipes/{{bomHeaderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "recipes",
                "{{bomHeaderId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"labour_cost\": \"195\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Save Recipe Lines",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/recipes/{{bomHeaderId}}/lines",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "recipes",
                "{{bomHeaderId}}",
                "lines"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"component_variant_id\": \"{{variantId}}\",\n      \"quantity\": \"1.2\",\n      \"uom_id\": \"{{uomId}}\",\n      \"wastage_percent\": \"3\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cost Recipe",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/recipes/{{bomHeaderId}}/cost",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "recipes",
                "{{bomHeaderId}}",
                "cost"
              ]
            }
          }
        },
        {
          "name": "Approve Recipe",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/recipes/{{bomHeaderId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "recipes",
                "{{bomHeaderId}}",
                "approve"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"effective_from\": \"2026-07-01\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Open New Version",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/recipes/{{bomHeaderId}}/new-version",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "recipes",
                "{{bomHeaderId}}",
                "new-version"
              ]
            }
          }
        },
        {
          "name": "Delete Recipe Draft",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/recipes/{{bomHeaderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "recipes",
                "{{bomHeaderId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Catalogue - Warranty & Serial Register",
      "description": "T019 / Screen 4.6.\n\nWarranty cover STARTS AUTOMATICALLY when a serialised unit is sold. That rule is a listener on the domain event bus, so Phase 4's sale only has to fire `sale.completed` with `serial_ids` in its payload — there is nothing for the sale code to call.\n\nThe dates are COPIED onto the serial record when the unit sells, so editing a template later cannot change cover a customer already has.\n\n`serials/lookup` is what the counter scans during a return, and is deliberately NOT branch-scoped: a unit sold in Colombo is often returned in Kandy.\n\nSerials are normally created by goods receipt (Phase 2). POST /serials exists for opening-balance stock and corrections.",
      "item": [
        {
          "name": "List Warranty Templates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/warranty-templates?status=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "warranty-templates"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Create Warranty Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/warranty-templates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "warranty-templates"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Standard 12 months\",\n  \"duration_months\": 12,\n  \"duration_days\": 0,\n  \"terms\": \"Manufacturing defects only. Excludes physical damage.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update Warranty Template",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/warranty-templates/{{warrantyTemplateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "warranty-templates",
                "{{warrantyTemplateId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"duration_months\": 24\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Warranty Template",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/warranty-templates/{{warrantyTemplateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "warranty-templates",
                "{{warrantyTemplateId}}"
              ]
            }
          }
        },
        {
          "name": "Serial Register",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/serials?search=&product_variant_id=&status=&branch_id=&under_warranty=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "serials"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "product_variant_id",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "branch_id",
                  "value": ""
                },
                {
                  "key": "under_warranty",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Counter Lookup (return desk)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/serials/lookup?serial=356938035643810",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "serials",
                "lookup"
              ],
              "query": [
                {
                  "key": "serial",
                  "value": "356938035643810"
                }
              ]
            }
          }
        },
        {
          "name": "Register Serials",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/serials",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "serials"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": \"{{variantId}}\",\n  \"branch_id\": \"{{branchId}}\",\n  \"serials\": [\n    \"356938035643809\",\n    \"356938035643810\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Correct a Serial",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/serials/{{serialId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "serials",
                "{{serialId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"damaged\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Inventory - Movement Ledger (Screen 4.3)",
      "item": [
        {
          "name": "List Movements",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/movements?from=&to=&product_variant_id=&movement_type=&reference=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "movements"
              ],
              "query": [
                {
                  "key": "from",
                  "value": ""
                },
                {
                  "key": "to",
                  "value": ""
                },
                {
                  "key": "product_variant_id",
                  "value": ""
                },
                {
                  "key": "movement_type",
                  "value": ""
                },
                {
                  "key": "reference",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "The append-only ledger. Branch-scoped: a clerk sees their own branch, head office sees whichever branch the topbar selector is on. Quantities are signed decimal STRINGS — a receipt is positive, a sale negative — because SUM(quantity) for a SKU in a branch IS its on-hand quantity."
          }
        },
        {
          "name": "Movement Types",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/movement-types",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "movement-types"
              ]
            },
            "description": "The B2 movement types, for the Screen 4.3 filter. Each type has a fixed direction; only `adjustment` may go either way, which is why B2 requires it to carry a reason code."
          }
        },
        {
          "name": "List Balances",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/balances?product_variant_id=&search=&in_stock=&negative=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "balances"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": ""
                },
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "in_stock",
                  "value": ""
                },
                {
                  "key": "negative",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "A PROJECTION of the ledger, not a second source of truth — it exists so a till does not sum a million rows. `available` is a database-generated column (on_hand - reserved), so nothing in application code can write a value that contradicts its own inputs. `negative=1` finds stock that has gone below zero, which always means something went unrecorded."
          }
        },
        {
          "name": "Reconcile a SKU",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/balances/{{variantId}}/reconcile?branch_id={{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "balances",
                "{{variantId}}",
                "reconcile"
              ],
              "query": [
                {
                  "key": "branch_id",
                  "value": "{{branchId}}"
                }
              ]
            },
            "description": "Projection versus ledger for one SKU in one branch. This is the tool to reach for when a number looks wrong; T036 turns the same comparison into a release gate for the phase. When the two disagree, the ledger is right."
          }
        },
        {
          "name": "Stock By Branch (Screen 4.1)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/by-branch?search=&in_stock=&negative=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "by-branch"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "in_stock",
                  "value": ""
                },
                {
                  "key": "negative",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "One row per SKU with a number per branch. Paged by SKU, not by balance row — paginating the rows would split a product across pages and make its totals lie. Which branches appear is the branch scope's decision: a branch user sees their own, head office sees them all, and head office with a branch pinned sees just that one."
          }
        },
        {
          "name": "Stock Detail For One SKU (Screen 4.2)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/variants/{{variantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "variants",
                "{{variantId}}"
              ]
            },
            "description": "Balances, cost layers, recent movements and holds in ONE call — four round trips to build one screen is four chances for the numbers to disagree. Each branch line carries its own projection-vs-ledger verdict, and `totals.value` comes from the layers rather than a quantity times a \"current cost\"."
          }
        }
      ]
    },
    {
      "name": "Inventory - Cost Layers (T024)",
      "item": [
        {
          "name": "List Cost Layers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/batches?product_variant_id={{variantId}}&status=open&order=fifo",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "batches"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": "{{variantId}}"
                },
                {
                  "key": "status",
                  "value": "open"
                },
                {
                  "key": "order",
                  "value": "fifo"
                }
              ]
            },
            "description": "A batch IS a cost layer. `order` picks FIFO (receipt order) or FEFO (earliest expiry, with never-expiring layers last). Read only: a layer is created by receiving goods, never by typing one."
          }
        },
        {
          "name": "Get Cost Layer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/batches/{{batchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "batches",
                "{{batchId}}"
              ]
            }
          }
        },
        {
          "name": "Stock Valuation",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/batches/valuation?product_variant_id={{variantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "batches",
                "valuation"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": "{{variantId}}"
                }
              ]
            },
            "description": "Value from the layers and quantity from the ledger side by side. `agrees` false means something moved stock without costing it — the drift the Phase 2 exit harness gates on."
          }
        },
        {
          "name": "Expiry Watchlist",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/batches/expiring?days=30",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "batches",
                "expiring"
              ],
              "query": [
                {
                  "key": "days",
                  "value": "30"
                }
              ]
            },
            "description": "Already expired and still on the shelf, separated from expiring soon."
          }
        }
      ]
    },
    {
      "name": "Inventory - Reservations (T026)",
      "item": [
        {
          "name": "List Reservations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/reservations?product_variant_id={{variantId}}&status=active",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "reservations"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": "{{variantId}}"
                },
                {
                  "key": "status",
                  "value": "active"
                }
              ]
            }
          }
        },
        {
          "name": "Reserve Stock",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/reservations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "reservations"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": \"{{variantId}}\",\n  \"quantity\": \"3\",\n  \"hold_minutes\": 30,\n  \"reference_number\": \"CALL-1042\",\n  \"notes\": \"Held for a phone order\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Moves `reserved`, never `on_hand` — the goods are still on the shelf. The check and the increment are ONE conditional UPDATE, so two customers cannot both reserve the last unit. Answers 409 (not 422) when the stock has gone: the request was fine, the world moved."
          }
        },
        {
          "name": "Release Reservation",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/reservations/{{reservationId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "reservations",
                "{{reservationId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Customer changed their mind\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Reconcile Reserved",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/reservations/reconcile?product_variant_id={{variantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "reservations",
                "reconcile"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": "{{variantId}}"
                }
              ]
            },
            "description": "`stock_balances.reserved` against the sum of active reservations."
          }
        }
      ]
    },
    {
      "name": "Inventory - Stock Policy & Low Stock (T027)",
      "item": [
        {
          "name": "List Stock Policy",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/settings?product_variant_id={{variantId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "settings"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": "{{variantId}}"
                }
              ]
            }
          }
        },
        {
          "name": "Set Stock Policy",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/settings",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "settings"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": \"{{variantId}}\",\n  \"reorder_point\": \"5\",\n  \"reorder_qty\": \"24\",\n  \"max_qty\": \"30\",\n  \"safety_stock\": \"2\",\n  \"lead_time_days\": 7,\n  \"allow_negative\": null\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Per SKU per BRANCH — the same product behaves differently in different shops. Every field is nullable and null means \"no policy\", not zero: a null reorder point means \"do not watch this\", a zero one means \"tell me when it runs out\". An omitted field is CLEARED, so there is a way to say null. `allow_negative` is tri-state; null inherits the branch."
          }
        },
        {
          "name": "Low Stock",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock/low-stock?branch_id={{branchId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock",
                "low-stock"
              ],
              "query": [
                {
                  "key": "branch_id",
                  "value": "{{branchId}}"
                }
              ]
            },
            "description": "Measured against AVAILABLE, not on-hand — stock already promised to somebody else will not fill a shelf."
          }
        }
      ]
    },
    {
      "name": "Purchasing - Suppliers (T028)",
      "item": [
        {
          "name": "List Suppliers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers?search=&status=&with_balance=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "with_balance",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Create Supplier",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"SUP-100\",\n  \"name\": \"Lanka Textiles (Pvt) Ltd\",\n  \"currency\": \"USD\",\n  \"payment_terms_days\": 30,\n  \"tax_number\": \"\",\n  \"contact_name\": \"\",\n  \"email\": \"\",\n  \"phone\": \"\",\n  \"city\": \"Colombo\",\n  \"country\": \"Sri Lanka\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Supplier",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers/{{supplierId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers",
                "{{supplierId}}"
              ]
            }
          }
        },
        {
          "name": "Update Supplier",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers/{{supplierId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers",
                "{{supplierId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Lanka Textiles (Pvt) Ltd\",\n  \"payment_terms_days\": 45\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Supplier",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers/{{supplierId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers",
                "{{supplierId}}"
              ]
            },
            "description": "Refused while anything is owed — deleting would take the debt off the ageing report while the money is still due."
          }
        },
        {
          "name": "Supplier Statement",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers/{{supplierId}}/statement?from=&to=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers",
                "{{supplierId}}",
                "statement"
              ],
              "query": [
                {
                  "key": "from",
                  "value": ""
                },
                {
                  "key": "to",
                  "value": ""
                }
              ]
            },
            "description": "Every ledger entry with a running balance, plus a projection-vs-entries reconciliation."
          }
        },
        {
          "name": "Supplier Ageing",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers/ageing?supplier_id=&as_at=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers",
                "ageing"
              ],
              "query": [
                {
                  "key": "supplier_id",
                  "value": ""
                },
                {
                  "key": "as_at",
                  "value": ""
                }
              ]
            },
            "description": "Only invoices and opening balances age. Payments and debit notes are applied OLDEST FIRST against them, so the buckets add up to the balance rather than exceeding it."
          }
        },
        {
          "name": "Post Opening Balance",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/suppliers/{{supplierId}}/opening-balance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "suppliers",
                "{{supplierId}}",
                "opening-balance"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"2500.0000\",\n  \"entry_date\": \"2026-01-01\",\n  \"notes\": \"Go-live balance\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Separately permissioned (`purchasing.opening`) and one-shot: a second would quietly double what the business thinks it owes."
          }
        }
      ]
    },
    {
      "name": "Purchasing - Purchase Orders (T029)",
      "item": [
        {
          "name": "Purchase Order Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "statuses"
              ]
            },
            "description": "The eight C6 states: draft, submitted, approved, sent, partially_received, fully_received, closed, cancelled."
          }
        },
        {
          "name": "List Purchase Orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders?status=&supplier_id=&open=&search=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "supplier_id",
                  "value": ""
                },
                {
                  "key": "open",
                  "value": ""
                },
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Raise Purchase Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supplier_id\": \"{{supplierId}}\",\n  \"currency\": \"USD\",\n  \"exchange_rate\": \"302.50\",\n  \"expected_at\": \"2026-09-15\",\n  \"supplier_reference\": \"\",\n  \"lines\": [\n    {\n      \"product_variant_id\": \"{{variantId}}\",\n      \"quantity\": \"10\",\n      \"unit_price\": \"1600.0000\",\n      \"uom_id\": null,\n      \"tax_class_id\": null\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Quantities stay in the SUPPLIER’S unit — a case of 24, a 50 kg sack — and are converted at goods receipt. The order captures the five B11 currency facts; an explicit `exchange_rate` wins over the rate table, because the order was agreed at a rate."
          }
        },
        {
          "name": "Get Purchase Order",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}"
              ]
            }
          }
        },
        {
          "name": "Update Purchase Order Lines",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": \"{{variantId}}\",\n      \"quantity\": \"12\",\n      \"unit_price\": \"1600.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Whole-list replacement. Refused once the order is Sent — the supplier has seen it."
          }
        },
        {
          "name": "Submit Purchase Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}/submit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}",
                "submit"
              ]
            }
          }
        },
        {
          "name": "Approve Purchase Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}",
                "approve"
              ]
            },
            "description": "Approval limits: at or below `purchasing.approval_threshold` (BASE currency) whoever raised it may approve; above it `purchasing.approve` is required. A threshold of zero means every order needs the permission."
          }
        },
        {
          "name": "Send Purchase Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}/send",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}",
                "send"
              ]
            }
          }
        },
        {
          "name": "Return To Draft",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}/return",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}",
                "return"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Wrong quantity on line 1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Withdraws the approval with it — a returned order is not an approved one."
          }
        },
        {
          "name": "Close Purchase Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}",
                "close"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Supplier cannot fulfil the balance\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "For a short-shipped order nobody expects the rest of, so it stops clogging the receiving screen."
          }
        },
        {
          "name": "Cancel Purchase Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/purchase-orders/{{purchaseOrderId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "purchase-orders",
                "{{purchaseOrderId}}",
                "cancel"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Ordered by mistake\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Refused once anything has been received: the stock is on the shelf, and cancelling would leave nothing explaining where it came from."
          }
        }
      ]
    },
    {
      "name": "Purchasing - Goods Receipts & Landed Costs (T030/T031)",
      "description": "Receiving is the moment stock exists. Batches, expiry dates and serials are captured HERE and nowhere else — once the goods are on the shelf nobody can reconstruct which delivery a particular expiry date belonged to.\n\nA receipt is a draft until it is posted. Posting writes the ledger, creates the cost layers, registers the serials, advances the purchase order and fires `goods_receipt.posted` (which queues shelf labels). There is no unpost — a mistake is corrected by a supplier return or a stock adjustment.\n\nLanded costs (T031) spread freight, duty and handling across the lines of a posted or draft receipt and INTO the layer cost, by value, quantity, weight or a manual split. The remainder of a rounded split lands on the last line so the allocation always sums back to the cost.",
      "item": [
        {
          "name": "Goods Receipt Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "statuses"
              ]
            },
            "description": "draft, posted, cancelled. There is no \"approved\" step — the goods are physically on the loading bay."
          }
        },
        {
          "name": "List Goods Receipts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts?status=posted",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "posted"
                }
              ]
            },
            "description": "Branch-scoped like every stock document."
          }
        },
        {
          "name": "Outstanding Lines For A PO",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/outstanding/{{purchaseOrderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "outstanding",
                "{{purchaseOrderId}}"
              ]
            },
            "description": "What the order still expects, so the wizard prefills the BALANCE rather than the whole order — partial receipt across several deliveries is the normal case."
          }
        },
        {
          "name": "Create Goods Receipt (draft)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts"
              ]
            },
            "description": "`quantity` is in the SUPPLIER's unit; the service converts it to stock units and stores the factor on the line, so the receipt still reproduces if the unit is later redefined. A serial-tracked line needs exactly one serial per unit.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supplier_id\": 1,\n  \"purchase_order_id\": 1,\n  \"supplier_reference\": \"DN-40021\",\n  \"received_at\": \"2026-08-26\",\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"purchase_order_line_id\": 1,\n      \"quantity\": \"10.000000\",\n      \"unit_price\": \"100.0000\",\n      \"batch_no\": \"B-2608\",\n      \"expiry_date\": \"2027-08-26\",\n      \"serials\": [\n        \"SN-0001\",\n        \"SN-0002\"\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Goods Receipt",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}"
              ]
            },
            "description": "Lines carry both quantities, the conversion factor between them, and the unit cost once landed costs have been spread."
          }
        },
        {
          "name": "Update Goods Receipt Lines",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}"
              ]
            },
            "description": "Draft only. A posted receipt has moved stock and is history.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"9.000000\",\n      \"unit_price\": \"100.0000\",\n      \"batch_no\": \"B-2608\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Post Goods Receipt",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}",
                "post"
              ]
            },
            "description": "One-way door: ledger, cost layers, serial register, PO progress, `goods_receipt.posted`. Needs `purchasing.receive`.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel Goods Receipt",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}",
                "cancel"
              ]
            },
            "description": "Draft only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Delivery refused at the gate\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Landed Costs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}/landed-costs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}",
                "landed-costs"
              ]
            },
            "description": "Each cost with its per-line allocation AND the `basis` the split was computed from — the arithmetic, not just the result."
          }
        },
        {
          "name": "Add Landed Cost",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}/landed-costs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}",
                "landed-costs"
              ]
            },
            "description": "`allocation_method` is value, quantity, weight or manual. `manual` (line id => amount) is required for the manual method and must sum to the amount.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"cost_type\": \"freight\",\n  \"description\": \"Sea freight, container CGMU4471\",\n  \"amount\": \"1000.0000\",\n  \"allocation_method\": \"value\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Remove Landed Cost",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/goods-receipts/{{goodsReceiptId}}/landed-costs/{{landedCostId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "goods-receipts",
                "{{goodsReceiptId}}",
                "landed-costs",
                "{{landedCostId}}"
              ]
            },
            "description": "Re-spreads what is left across the lines, so the layers stay consistent with the costs that remain."
          }
        }
      ]
    },
    {
      "name": "Purchasing - Supplier Invoices & Three-Way Match (T032)",
      "description": "The invoice is checked against TWO documents: quantity against the goods receipt (what actually arrived) and price against the purchase order (what was agreed). Billing the ordered quantity when less turned up is the most common overcharge in procurement, and comparing the invoice with the order alone never finds it.\n\nEntering an invoice needs `purchasing.invoice`. Approving one that did NOT match additionally needs `purchasing.approve` and a written reason — it is the decision to pay something other than what was agreed.",
      "item": [
        {
          "name": "Supplier Invoice Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices",
                "statuses"
              ]
            },
            "description": "Statuses AND the match tolerances, so a screen can explain a variance rather than just flag it."
          }
        },
        {
          "name": "List Supplier Invoices",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices?status=matched&attention=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "matched"
                },
                {
                  "key": "attention",
                  "value": "1"
                }
              ]
            },
            "description": "`attention=1` narrows to invoices that did not match cleanly — the working queue for accounts payable."
          }
        },
        {
          "name": "Enter Supplier Invoice",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices"
              ]
            },
            "description": "Lines carrying `goods_receipt_line_id` are matched; lines without one are service charges and come back `unmatched` rather than being silently accepted.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supplier_id\": 1,\n  \"purchase_order_id\": 1,\n  \"invoice_no\": \"INV-88213\",\n  \"invoice_date\": \"2026-08-26\",\n  \"due_date\": \"2026-09-25\",\n  \"lines\": [\n    {\n      \"goods_receipt_line_id\": 1,\n      \"product_variant_id\": 1,\n      \"quantity\": \"10.000000\",\n      \"unit_price\": \"100.0000\"\n    },\n    {\n      \"description\": \"Freight\",\n      \"quantity\": \"1.000000\",\n      \"unit_price\": \"45.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Supplier Invoice",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices/{{supplierInvoiceId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices",
                "{{supplierInvoiceId}}"
              ]
            },
            "description": "Returns each line with what it was compared against — `expected_quantity` from the receipt, `expected_unit_price` from the order — plus the per-line verdict."
          }
        },
        {
          "name": "Re-run Match",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices/{{supplierInvoiceId}}/match",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices",
                "{{supplierInvoiceId}}",
                "match"
              ]
            },
            "description": "A later change to the order or the receipt moves the goalposts. Re-running the comparison is explicit rather than automatic, so a variance cannot quietly vanish.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Approve Supplier Invoice",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices/{{supplierInvoiceId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices",
                "{{supplierInvoiceId}}",
                "approve"
              ]
            },
            "description": "Raises the payable. With a variance this needs `purchasing.approve` AND `variance_reason`; the approver's name is stored against it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"variance_reason\": \"Supplier confirmed the price rise by email on 12 Aug\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel Supplier Invoice",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-invoices/{{supplierInvoiceId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-invoices",
                "{{supplierInvoiceId}}",
                "cancel"
              ]
            },
            "description": "Only before approval — once the payable is raised the invoice is history.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Duplicate of INV-88213\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Purchasing - Supplier Returns & Payments (T033)",
      "description": "The two ways what we owe a supplier goes DOWN. A return moves stock, a payment moves money; they are otherwise unrelated.\n\nA return line names a stock BATCH, not just a SKU: the goods go back at the cost they arrived at. A payment allocates across several invoices because one cheque routinely settles several, and settling a foreign invoice at today's rate when it was raised at last month's produces a realised gain or loss that is captured, not swept into cost.",
      "item": [
        {
          "name": "List Supplier Returns",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-returns?status=draft",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-returns"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "draft"
                }
              ]
            },
            "description": "Debit notes raised against a supplier."
          }
        },
        {
          "name": "Create Supplier Return",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-returns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-returns"
              ]
            },
            "description": "`stock_batch_id` is required per line — a return with no layer cannot send the goods back at the cost they arrived at.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supplier_id\": 1,\n  \"goods_receipt_id\": 1,\n  \"reason\": \"damaged\",\n  \"disposition\": \"credit_note\",\n  \"lines\": [\n    {\n      \"stock_batch_id\": 1,\n      \"goods_receipt_line_id\": 1,\n      \"quantity\": \"2.000000\",\n      \"serials\": [\n        \"SN-0001\",\n        \"SN-0002\"\n      ]\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Supplier Return",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-returns/{{supplierReturnId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-returns",
                "{{supplierReturnId}}"
              ]
            },
            "description": "Lines carry the layer cost the goods are going back at."
          }
        },
        {
          "name": "Post Supplier Return",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-returns/{{supplierReturnId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-returns",
                "{{supplierReturnId}}",
                "post"
              ]
            },
            "description": "Takes the stock off the shelf against the named batch and raises the debit note. There is no unpost.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel Supplier Return",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-returns/{{supplierReturnId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-returns",
                "{{supplierReturnId}}",
                "cancel"
              ]
            },
            "description": "Draft only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Supplier agreed to replace instead\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Supplier Payments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-payments?status=posted",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-payments"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "posted"
                }
              ]
            },
            "description": "`fx_difference` is the realised gain or loss on a foreign settlement."
          }
        },
        {
          "name": "Outstanding Invoices For A Supplier",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-payments/outstanding/{{supplierId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-payments",
                "outstanding",
                "{{supplierId}}"
              ]
            },
            "description": "What can still be settled, oldest due first — the allocation grid is built from this."
          }
        },
        {
          "name": "Draft Supplier Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-payments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-payments"
              ]
            },
            "description": "`allocations` is invoice id => amount. The payment must equal what it allocates: a cheque for more than the invoices it settles is a credit on account, not a payment.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"supplier_id\": 1,\n  \"amount\": \"1450.0000\",\n  \"method\": \"bank_transfer\",\n  \"payment_reference\": \"FT26082600991\",\n  \"paid_on\": \"2026-08-26\",\n  \"allocations\": {\n    \"1\": \"1000.0000\",\n    \"2\": \"450.0000\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get Supplier Payment",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-payments/{{supplierPaymentId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-payments",
                "{{supplierPaymentId}}"
              ]
            },
            "description": "Allocations carry the rate the INVOICE was raised at, the other half of the FX difference."
          }
        },
        {
          "name": "Post Supplier Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-payments/{{supplierPaymentId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-payments",
                "{{supplierPaymentId}}",
                "post"
              ]
            },
            "description": "Settles the allocated invoices and brings the supplier balance down. There is no unpost.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel Supplier Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/supplier-payments/{{supplierPaymentId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "supplier-payments",
                "{{supplierPaymentId}}",
                "cancel"
              ]
            },
            "description": "Draft only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Wrong bank account\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Production - Manufactured Stock (T035)",
      "description": "A run is the one act that changes two SKUs' stock at once: components out, output in. BOTH halves reach the ledger — a `production_consume` per component and a `production_output` for what was made — because netting them into one \"conversion\" movement would break the invariant that SUM(quantity) per (branch, SKU) IS on-hand, for the components AND the product.\n\nRELEASING COPIES THE RECIPE onto the run: version, yield, labour, overhead and every component line. Approving a new recipe version tomorrow cannot restate what was made today (B1).\n\nThe output costs what the components ACTUALLY cost — read back from the layers the costing engine consumed — plus the captured labour and overhead. Not a standard cost.\n\nThere is no unpost. A completed run is undone by a reversal, which writes compensating movements keeping the ORIGINAL movement types with the opposite sign, and returns the components at the cost they left at.",
      "item": [
        {
          "name": "Production Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "statuses"
              ]
            },
            "description": "draft, released, completed, cancelled, reversed."
          }
        },
        {
          "name": "List Production Runs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders?status=&open=&search=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "open",
                  "value": ""
                },
                {
                  "key": "search",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`open=1` narrows to runs that have not finished — the shop-floor working list."
          }
        },
        {
          "name": "Open A Run",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders"
              ]
            },
            "description": "Picks up the recipe version in force unless `bom_header_id` names one. Reproducing an old run is a real need, so naming a version is allowed — but it still has to be that product's.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": 1,\n  \"quantity_planned\": \"10.000000\",\n  \"notes\": \"Morning bake\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Run",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/{{productionOrderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "{{productionOrderId}}"
              ]
            },
            "description": "Lines carry the recipe AS IT WAS on this run, plus what each component actually cost."
          }
        },
        {
          "name": "Component Requirements",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/{{productionOrderId}}/requirements",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "{{productionOrderId}}",
                "requirements"
              ]
            },
            "description": "What the run will draw and whether it is there. Asked BEFORE completing — discovering a shortage halfway through means the components already consumed are gone."
          }
        },
        {
          "name": "Release A Run",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/{{productionOrderId}}/release",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "{{productionOrderId}}",
                "release"
              ]
            },
            "description": "Copies the recipe onto the run and works out what it will draw. Only an APPROVED recipe version can be put into production.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Complete A Run",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/{{productionOrderId}}/complete",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "{{productionOrderId}}",
                "complete"
              ]
            },
            "description": "`quantity_produced` is what actually came out — a run planned for 100 that yields 94 is the normal case, and components are consumed in proportion to what was MADE, not to what was planned. `consumed` overrides a line where the operator drew something else.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"quantity_produced\": \"9.000000\",\n  \"consumed\": {\n    \"1\": \"25.000000\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Reverse A Run",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/{{productionOrderId}}/reverse",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "{{productionOrderId}}",
                "reverse"
              ]
            },
            "description": "Creates a compensating run: the output comes off the shelf and the components go back at the cost they left at. Returning them at today's cost would invent a gain or a loss out of a data-entry mistake. A run can only be reversed once.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Wrong flour used\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel A Run",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/production-orders/{{productionOrderId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "production-orders",
                "{{productionOrderId}}",
                "cancel"
              ]
            },
            "description": "Only a run that has moved no stock. A completed one is undone by a reversal.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Order withdrawn\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Transfers - Stock Requests (T037)",
      "description": "One branch asking another for stock. A CONVERSATION, not a movement: nothing leaves a shelf here.\n\nA REQUEST IS NOT A RESERVATION. Approving a line does not hold the stock — a request approved on Monday and dispatched on Thursday would otherwise starve the fulfilling branch's own customers for three days over goods that had not moved. Stock is committed at dispatch (T038) and not a moment earlier.\n\nVisible from BOTH ends: the asking branch must see what it asked for and the fulfilling branch what it has been asked for. Approval is per LINE, because \"we can send you 6 of the 10, and none of the other item\" is the normal answer rather than an exception.",
      "item": [
        {
          "name": "Stock Request Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests",
                "statuses"
              ]
            },
            "description": "draft, submitted, approved, partially_approved, rejected, fulfilled, cancelled. The three settled outcomes are kept apart because each leads to a different next action for the branch that asked."
          }
        },
        {
          "name": "List Stock Requests",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests?direction=inbox&status=&open=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests"
              ],
              "query": [
                {
                  "key": "direction",
                  "value": "inbox"
                },
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "open",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`direction=inbox` is what this branch has been ASKED for; `outbox` is what it has asked others for. Both are meaningless without an active branch, so head office sees everything."
          }
        },
        {
          "name": "Raise A Request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests"
              ]
            },
            "description": "`from_branch_id` is who wants the stock; `to_branch_id` is who is being asked. Neither is defaulted from the active branch — \"transfer to wherever I happen to be standing\" is not a thing anybody means.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to_branch_id\": 2,\n  \"priority\": \"high\",\n  \"required_by\": \"2026-09-02\",\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"10.000000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Request",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests/{{stockRequestId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests",
                "{{stockRequestId}}"
              ]
            },
            "description": "Lines carry `outstanding` — approved but not yet shipped, which is what a transfer can still carry."
          }
        },
        {
          "name": "Update A Draft",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests/{{stockRequestId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests",
                "{{stockRequestId}}"
              ]
            },
            "description": "Draft only. Once submitted the fulfilling branch is looking at it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"25.000000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Submit A Request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests/{{stockRequestId}}/submit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests",
                "{{stockRequestId}}",
                "submit"
              ]
            },
            "description": "Notifies the fulfilling branch. That notification is the point of the step — a request sitting in a table nobody looks at is the phone call this document exists to replace.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Review A Request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests/{{stockRequestId}}/review",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests",
                "{{stockRequestId}}",
                "review"
              ]
            },
            "description": "`decisions` is line id => {quantity, reject_reason}. Zero rejects the line; less than was asked for is a partial; MORE is refused rather than capped, because it is always a typo. A line left out is rejected — leaving one unanswered would let the request sit in Submitted forever with no sign anybody had looked.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"decisions\": {\n    \"1\": {\n      \"quantity\": \"6.000000\"\n    }\n  },\n  \"notes\": \"Sending what we can spare\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel A Request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-requests/{{stockRequestId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-requests",
                "{{stockRequestId}}",
                "cancel"
              ]
            },
            "description": "Allowed right up until stock ships. An approved request that has not been acted on is still just a conversation.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"No longer needed\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Transfers - Stock Transfers & In-Transit (T038)",
      "description": "WHERE IS THE STOCK WHILE IT IS ON THE VAN?\n\nDispatch posts `transfer_out` at the sender; receipt posts `transfer_in` at the receiver. Between those moments the goods are in NEITHER branch's on-hand, and that is correct — the sender cannot sell them and the receiver does not have them. The ledger invariant holds at both ends throughout.\n\nIn-transit is therefore a property of the DOCUMENT, not a balance row, and a transfer nobody receives shows up as a line that never clears — which is exactly what needs chasing.\n\nTHE COST TRAVELS. Dispatch consumes the sender's layers through the costing engine and records what each unit actually cost; receipt recreates those layers, one per source layer, at the same cost. Re-costing on arrival would let a business manufacture margin by driving a van around.\n\nCANCELLATION CUT-OFF (B4): drafts only. Once dispatched the sender's stock is already gone, so a transfer that went wrong is RECEIVED short or damaged — because that is what happened.",
      "item": [
        {
          "name": "Transfer Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "statuses"
              ]
            },
            "description": "draft, in_transit, received, partially_received, cancelled. `in_transit` is a real state, not a gap between two others."
          }
        },
        {
          "name": "List Transfers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers?direction=inbound&status=in_transit&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers"
              ],
              "query": [
                {
                  "key": "direction",
                  "value": "inbound"
                },
                {
                  "key": "status",
                  "value": "in_transit"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`direction=inbound` is what is coming to this branch; `outbound` what is leaving it."
          }
        },
        {
          "name": "In-Transit Stock",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/in-transit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "in-transit"
              ]
            },
            "description": "Everything on a van right now, by SKU and destination, with `days_overdue` on anything past its expected date. Read off the transfer documents rather than a balance row, because in-transit stock is in nobody's on-hand by design."
          }
        },
        {
          "name": "Raise A Transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers"
              ]
            },
            "description": "Name a `stock_request_id` to prefill what that request still has outstanding, or give `lines` directly — head office pushing seasonal stock out to the shops is not a conversation, and forcing a request first would be ceremony. A transfer against a request must run the same way round as it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to_branch_id\": 2,\n  \"carrier\": \"Own van\",\n  \"expected_at\": \"2026-08-28\",\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"20.000000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Transfer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/{{stockTransferId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "{{stockTransferId}}"
              ]
            },
            "description": "Lines carry `in_transit` and the travelling `layers` — one row per source layer, so the receiving branch's FIFO sees what actually left rather than an average of it."
          }
        },
        {
          "name": "Update A Draft",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/{{stockTransferId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "{{stockTransferId}}"
              ]
            },
            "description": "Draft only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"18.000000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Dispatch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/{{stockTransferId}}/dispatch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "{{stockTransferId}}",
                "dispatch"
              ]
            },
            "description": "The sender's stock leaves NOW. This is the moment stock is committed — not when the request was approved. Batch identity and expiry travel with the goods; a transfer that reset an expiry date is how out-of-date stock gets sold.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Receive",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/{{stockTransferId}}/receive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "{{stockTransferId}}",
                "receive"
              ]
            },
            "description": "`receipts` is line id => {received, damaged, reason}. What is neither received nor damaged is SHORT — derived rather than typed, because a form asking for all three invites them not to add up. Only what is ACCEPTED becomes stock: booking in the missing units so the document balances is the one thing that would make the transfer unauditable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"receipts\": {\n    \"1\": {\n      \"received\": \"17.000000\",\n      \"damaged\": \"3.000000\",\n      \"reason\": \"Crushed in transit\"\n    }\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel A Transfer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-transfers/{{stockTransferId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-transfers",
                "{{stockTransferId}}",
                "cancel"
              ]
            },
            "description": "Drafts only (B4).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Van broke down, re-raising tomorrow\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Inventory - Stock Adjustments (T039, Screen 4.4)",
      "description": "The only document that may move stock in EITHER direction, and therefore the only one that has to justify itself. Every other movement is explained by what happened — goods arrived, goods sold. An adjustment says \"the number was wrong\", which without a reason and a signature is indistinguishable from somebody making the number say what they wanted.\n\nTHE THRESHOLD IS ON VALUE, NOT QUANTITY. 10,000 screws worth 200 and 2 phones worth 200,000 are not comparable risks. It is compared on GROSS, so a document writing 100 off one SKU and 100 onto another cannot slip under by netting to zero, and a threshold of zero means \"approve everything\".\n\nAPPROVAL IS SEGREGATED: `inventory.adjust` raises one, `inventory.adjust.approve` lets it post above the threshold. Whoever counted the shelf must not also sign off the difference.\n\nPosted is FINAL — a mistake is corrected by another adjustment, never by unposting, because the ledger has to keep showing that the number was changed.",
      "item": [
        {
          "name": "Adjustment Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "options"
              ]
            },
            "description": "Statuses, the reason pick list AND the threshold in force — so a screen can say \"this will need approval\" while the person is still typing, rather than refusing it after they press save."
          }
        },
        {
          "name": "List Adjustments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments?status=&awaiting=&reason=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "awaiting",
                  "value": ""
                },
                {
                  "key": "reason",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`awaiting=1` is the approver's queue."
          }
        },
        {
          "name": "Raise An Adjustment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments"
              ]
            },
            "description": "`quantity` is SIGNED: negative writes off, positive writes on — the same convention as the ledger, so nobody has to translate. A write-on may name a `unit_cost`; without one it is valued at the SKU's current cost, because found stock has to be worth something and inventing a price is worse.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason_code\": \"damaged\",\n  \"reason_note\": \"Pallet dropped in the yard\",\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"-4.000000\",\n      \"reason_code\": \"damaged\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get An Adjustment",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}"
              ]
            },
            "description": "Lines carry `quantity_before` and `quantity_after` — without them, \"adjusted to 96\" cannot be told from \"adjusted by 96\" a month later."
          }
        },
        {
          "name": "Update A Draft",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}"
              ]
            },
            "description": "Re-derives whether the document needs approving, and re-captures the threshold in force.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"-2.000000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Send For Approval",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}/submit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}",
                "submit"
              ]
            },
            "description": "Only meaningful above the threshold; below it the document posts straight from draft. Routing a write-off of three broken biscuits through a manager trains everybody to approve without looking.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Approve",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}",
                "approve"
              ]
            },
            "description": "Needs `inventory.adjust.approve`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Checked against the damage log\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Reject",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}/reject",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}",
                "reject"
              ]
            },
            "description": "Needs `inventory.adjust.approve`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Recount the shelf first\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Post",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}",
                "post"
              ]
            },
            "description": "Moves the stock. Write-offs consume real layers at what those layers cost; write-ons create a layer, because the valuation is computed from layers.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-adjustments/{{stockAdjustmentId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-adjustments",
                "{{stockAdjustmentId}}",
                "cancel"
              ]
            },
            "description": "Before posting only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Raised in error\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Inventory - Stock Takes (T040, Screen 4.5)",
      "description": "scope → sheets → blind counts → recount → variance approval → post as adjustments.\n\nTHE COUNT IS BLIND. `quantity_expected` is frozen when the sheet is generated and NEVER returned by the sheet endpoint. Given the system's number, a tired person at the end of a shift writes it down — and a stock take that agrees with the system by construction has told you nothing at considerable cost. `sheet` and `show` are separate endpoints for exactly this reason: one payload with a flag would put that figure one boolean away from the person who must not see it.\n\nA RECOUNT IS A SECOND OPINION, NOT AN EDIT. The first count is kept: \"counted 8, recounted 10\" and \"counted 10\" are different facts, and overwriting destroys the only evidence that a line was ever in doubt. A recount goes back against the SAME expected figures.\n\nAN UNCOUNTED LINE IS NOT A ZERO. Closing is refused while any line is uncounted — writing off stock because a sheet ran out of time would be the most damaging thing this feature could do.\n\nPOSTING GOES THROUGH AN ADJUSTMENT, so the result inherits the reason, threshold, approval and audit trail adjustments already carry.",
      "item": [
        {
          "name": "Stock Take Statuses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/statuses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "statuses"
              ]
            },
            "description": "draft, counting, review, approved, posted, cancelled. `review` sits between counting and approval because a variance is a question before it is a number."
          }
        },
        {
          "name": "List Stock Takes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts?status=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Open A Stock Take",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts"
              ]
            },
            "description": "`scope_type` is full, category, brand, location or manual. A FULL count covers every SKU the branch could hold, including ones the system thinks are at zero — a SKU sitting on a shelf that the system has forgotten is exactly what a stock take exists to find.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"scope_type\": \"manual\",\n  \"scope_values\": [\n    1,\n    2,\n    3\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Generate Sheets",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/sheets",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "sheets"
              ]
            },
            "description": "Freezes `quantity_expected` for every line. Frozen at sheet time rather than read at entry, so stock moving during the count cannot silently change what the count is measured against.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Count Sheet (blind)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/sheet?recount_only=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "sheet"
              ],
              "query": [
                {
                  "key": "recount_only",
                  "value": ""
                }
              ]
            },
            "description": "What the counter works from. Carries NO expected or previously-counted quantity. `recount_only=1` narrows it to the lines flagged for a second opinion."
          }
        },
        {
          "name": "Record Counts",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/counts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "counts"
              ]
            },
            "description": "`counts` is line id => quantity. A line counted a SECOND time keeps the first figure and stores the recount alongside it. A counted ZERO is a real finding, and distinct from a line nobody has reached.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"counts\": {\n    \"1\": \"96.000000\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Close Counting",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "close"
              ]
            },
            "description": "Works out the variances and moves to review. Refused while any line is uncounted.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Request A Recount",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/recount",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "recount"
              ]
            },
            "description": "Sends named lines back against the SAME expected figures — regenerating the sheets would reset the baseline and quietly absorb whatever moved in between.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"line_ids\": [\n    1,\n    2\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Approve The Variances",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "approve"
              ]
            },
            "description": "Needs `inventory.count.approve`. Counting and approving the variance are different jobs.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Recounted the shelf, figure stands\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Post",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "post"
              ]
            },
            "description": "Writes every variance as ONE stock adjustment. A count with no variances posts nothing and says so — an empty adjustment would put noise in the one report people read to find real problems.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Stock Take",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}"
              ]
            },
            "description": "The REVIEWER's view: carries the expected quantity, both counts and the variance. This is the payload the sheet deliberately is not."
          }
        },
        {
          "name": "Cancel",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/stock-counts/{{stockCountId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "stock-counts",
                "{{stockCountId}}",
                "cancel"
              ]
            },
            "description": "Before posting only.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Shop too busy, rescheduling\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Sales - Orders (T043)",
      "description": "One entity for every channel: a till sale, a phone order and a web order are all rows in `orders`, so \"what did we sell today\" needs no UNION. Only `/complete` moves stock or takes money, and it does both in ONE transaction (B5) — a half-posted sale is a receipt in a customer's hand with no stock movement behind it.",
      "item": [
        {
          "name": "Order Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "options"
              ]
            },
            "description": "Statuses, channels, payment methods and the currencies a till may be handed notes in. A screen never hard-codes an enum the API owns.\n\nEach method says whether it gives change (only cash does — anything else over the total is a refund waiting to happen) and whether it may be taken offline (a shared balance may not: two disconnected tills each seeing 500 available will each spend 500).\n\nEach currency carries today's rate so the payment dialog can show what a foreign note is worth as it is typed. That figure is for DISPLAY: the rate on the document is the one captured when the payment is actually taken, because that is the one the drawer is reconciled against. A currency with no rate today stays on the list, disabled — dropping it would leave a cashier holding notes and no way to record them."
          }
        },
        {
          "name": "List Orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders?status=completed&channel=pos&from=2026-08-01&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "completed"
                },
                {
                  "key": "channel",
                  "value": "pos"
                },
                {
                  "key": "from",
                  "value": "2026-08-01"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "Branch-scoped like every other document. `held=true` is what the POS \"resume a parked sale\" list calls."
          }
        },
        {
          "name": "List Held Sales",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders?held=true",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders"
              ],
              "query": [
                {
                  "key": "held",
                  "value": "true"
                }
              ]
            },
            "description": "Parked carts at this till's branch — the customer who went back for milk."
          }
        },
        {
          "name": "Show Order",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}"
              ]
            },
            "description": "Lines with their CAPTURED price, tax rate and description, plus the payments. A reprint years later reproduces the paper the customer was handed, so nothing here is re-derived from today's catalogue."
          }
        },
        {
          "name": "Start A Sale",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders"
              ]
            },
            "description": "`client_uuid` is generated by the till BEFORE the sale is saved locally: a replayed push returns the original order instead of selling the same goods twice. `channel` decides the numbering — INV for a till sale, ORD for a phone order — because they are different documents to a shop.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": 1,\n  \"channel\": \"pos\",\n  \"counter_id\": 1,\n  \"client_uuid\": \"{{$guid}}\",\n  \"customer_name\": \"Walk-in\",\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"2.000000\",\n      \"unit_price\": \"150.0000\"\n    },\n    {\n      \"product_variant_id\": 2,\n      \"quantity\": \"1.000000\",\n      \"unit_price\": \"480.0000\",\n      \"discount_amount\": \"30.0000\",\n      \"discount_reason\": \"Damaged packaging\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update The Cart",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}"
              ]
            },
            "description": "Replaces every line. A completed sale refuses this — editing what somebody already paid for is what a void and a fresh sale are for.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"3.000000\",\n      \"unit_price\": \"150.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Confirm The Order (Hold The Stock)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/confirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "confirm"
              ]
            },
            "description": "THE COMMITMENT POINT (T060, Screen 7.3). At a till the customer commits, the goods leave and the money arrives in one act. A phone order pulls those apart by hours or days, and from the moment the customer says yes the shop has PROMISED those units to them - so confirming RESERVES them. `reserved` goes up and `on_hand` does not move, because nothing has physically gone anywhere. Holding at dispatch instead would let the till sell the same last unit to somebody standing in the shop. If any line cannot be held the whole confirmation fails and every hold taken so far is rolled back: a half-reserved order is a promise the shop cannot keep. Needs `orders.confirm`, which is a separate permission from selling.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"required_by\": \"2026-09-04\",\n  \"fulfilment_method\": \"delivery\",\n  \"delivery_address_id\": 1,\n  \"taken_via\": \"whatsapp\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Reopen The Order For Changes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/unconfirm",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "unconfirm"
              ]
            },
            "description": "Puts a confirmed order back to draft and RELEASES its holds. Editing lines behind a live reservation would leave the hold pointing at quantities nobody agreed to, so this is a deliberate act rather than a side effect of typing - somebody has to know the stock is back on the shelf for anybody else to take."
          }
        },
        {
          "name": "Cancel The Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "cancel"
              ]
            },
            "description": "The order died before anything shipped. NOT a void: a void reverses movements that were posted, and a cancelled order never posted any - all that happens is the held stock going back and any coupon being released. Once the goods have gone this is refused, because that is a RETURN.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Customer changed their mind\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "What Is Held Against The Order",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/allocation",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "allocation"
              ]
            },
            "description": "What is ACTUALLY reserved, read off the reservations rather than the order lines. The order says what was promised; this says what is really held, and a screen that redisplayed the lines would report the promise as covered whether or not it is."
          }
        },
        {
          "name": "Fulfilment Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "options"
              ]
            },
            "description": "Shipment states, wave states and the allocation strategies. Each strategy carries its own DESCRIPTION, because a dropdown of three names with no explanation is a decision nobody can make - \"earliest promise first\" does not by itself say what happens to an order nobody promised anything about."
          }
        },
        {
          "name": "List Shipments",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments"
              ]
            },
            "description": "The picking screen's queue. Oldest first, because the thing that has been waiting longest is the thing to do next. `open=1` narrows it to what is still to go."
          }
        },
        {
          "name": "Plan A Shipment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments"
              ]
            },
            "description": "Plan one act of keeping an order's promise (T061, Screen 7.5). NOTHING MOVES: the units are already RESERVED by the order's confirmation (T060), held rather than moved, and they stay that way until the van goes. Only a CONFIRMED order can be picked - until the customer has committed, nothing is promised and nothing is held. Omit `lines` to send everything still outstanding, which is what a shop does most of the time.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": 1,\n  \"carrier\": \"Own van\",\n  \"lines\": [\n    {\n      \"order_line_id\": 1,\n      \"quantity\": \"6.000000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Shipment",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/{{fulfilmentId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "{{fulfilmentId}}"
              ]
            },
            "description": "One shipment with its lines, the order behind it and the ADDRESS VERSION it was picked against - not whatever the order says today."
          }
        },
        {
          "name": "Record What Was Picked",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/{{fulfilmentId}}/picked",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "{{fulfilmentId}}",
                "picked"
              ]
            },
            "description": "What the picker actually found. SHORT IS DERIVED from requested minus picked and never typed: a form asking for all three invites them not to add up. What is short stays OUTSTANDING on the order - it is not written off, because the customer was promised it and the shop has not said otherwise. More than was asked for is refused: that would take stock nobody reserved and bill the customer for goods they never agreed to.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"id\": 1,\n      \"quantity_picked\": \"8.000000\",\n      \"notes\": \"Two missing off the shelf\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Pack The Shipment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/{{fulfilmentId}}/pack",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "{{fulfilmentId}}",
                "pack"
              ]
            },
            "description": "The goods are in a box and checked. STILL NOTHING HAS MOVED - the box is standing in the shop. Kept apart from dispatch because \"is it ready?\" and \"has it gone?\" are the two different questions a customer actually asks. An empty box is refused: cancel the shipment instead."
          }
        },
        {
          "name": "Unpack The Shipment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/{{fulfilmentId}}/unpack",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "{{fulfilmentId}}",
                "unpack"
              ]
            },
            "description": "Something in the box was wrong. Back to picking."
          }
        },
        {
          "name": "Dispatch The Shipment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/{{fulfilmentId}}/dispatch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "{{fulfilmentId}}",
                "dispatch"
              ]
            },
            "description": "THE VAN GOES - the only endpoint here that moves stock. What was picked is issued through the costing engine and exactly that much of the order's hold is converted; converting the WHOLE hold on a part shipment would free stock still promised to this customer and the next till sale would take it. The negative-stock guard reads on_hand rather than available, which is what lets a shipment holding the last units actually go - they are reserved against this very order. When the last promised unit has gone the ORDER completes: the coupon is redeemed, the promotion counted and the debt raised, once, not once per van. Needs `orders.dispatch`, separately from picking. Send an Idempotency-Key: a retry must not ship twice.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"carrier\": \"Own van\",\n  \"tracking_reference\": \"VAN-1\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel The Shipment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/order-fulfilments/{{fulfilmentId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "order-fulfilments",
                "{{fulfilmentId}}",
                "cancel"
              ]
            },
            "description": "Available up to dispatch and NO FURTHER (B4) - once the van has gone the goods are with somebody and unwinding that is a RETURN, the same cut-off a stock transfer draws. Nothing is reversed because nothing was posted: the holds belong to the ORDER and simply stay standing for the next attempt.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Van broke down\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "What Is Still To Send",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/outstanding",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "outstanding"
              ]
            },
            "description": "Per line: what was promised and what is still owed. Requested minus everything picked on a shipment that has not been cancelled - a cancelled one gave its units back, so it owes nothing and must not reduce this."
          }
        },
        {
          "name": "Address History",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/addresses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "addresses"
              ]
            },
            "description": "Every address this order has been going to, oldest first. A dispatched shipment keeps pointing at the version it went out against, so a box can always be traced to the address on the note that travelled with it."
          }
        },
        {
          "name": "Redirect The Delivery",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/addresses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "addresses"
              ]
            },
            "description": "A customer rings back to send it somewhere else. This writes a NEW VERSION and the old one stays: overwriting would leave the picking note printed this morning disagreeing with the order, with no way to tell whether the box went to the wrong place or the record was changed afterwards. Shipments already dispatched keep their version; open ones follow the new address, because they have not gone anywhere yet.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_address_id\": 2,\n  \"reason\": \"Customer rang back - send it to work\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Picking Waves",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves"
              ]
            },
            "description": "One wave is one WALK of the shop covering many orders. `working=1` shows the ones still open or out with a picker."
          }
        },
        {
          "name": "Start A Picking Wave",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves"
              ]
            },
            "description": "A wave exists so a picker crosses the shop ONCE for twenty orders instead of twenty times. Name `order_ids` to pick them off a screen, or leave it out to sweep every confirmed order at this branch that is not already on somebody else's sheet - two pickers sent for the same units is exactly the walk this prevents. `allocation_strategy` decides who gets the stock when there is not enough for everybody (B4) and is CAPTURED onto the wave: changing the shop's policy next month must not rewrite the story of why one order went out in March and another did not.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": 1,\n  \"name\": \"Morning run\",\n  \"allocation_strategy\": \"promise_date\",\n  \"limit\": 20\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Picking Wave",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves/{{waveId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves",
                "{{waveId}}"
              ]
            },
            "description": "The wave with its pick sheet."
          }
        },
        {
          "name": "The Pick Sheet",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves/{{waveId}}/sheet",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves",
                "{{waveId}}",
                "sheet"
              ]
            },
            "description": "ONE ROW PER SKU, with the orders named against each. That is the whole point: a sheet that listed the orders one after another would leave the picker walking exactly as far as before. The orders travel with each row so the units can be attributed back when they are picked - otherwise the picker has a trolley and no idea which box each unit belongs in. Sorted by SKU so the walk is the same every time."
          }
        },
        {
          "name": "Add An Order To The Wave",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves/{{waveId}}/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves",
                "{{waveId}}",
                "orders"
              ]
            },
            "description": "Only while the wave is still OPEN. Adding an order to a sheet somebody is already walking means they finish without it, and nobody can tell whether it was missed or added late.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Release The Wave To A Picker",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves/{{waveId}}/release",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves",
                "{{waveId}}",
                "release"
              ]
            },
            "description": "Hand the sheet over. The wave stops accepting orders here, and that is the point: a list somebody is already walking cannot grow behind them.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"assigned_to\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Close The Wave",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves/{{waveId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves",
                "{{waveId}}",
                "close"
              ]
            },
            "description": "Everything on the sheet has gone or been called off. REFUSED while anything is still open, for the same reason a stock take refuses to close with an uncounted line: a wave closed over work nobody did is a box still sitting in the shop that the system says has left."
          }
        },
        {
          "name": "Cancel The Wave",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/picking-waves/{{waveId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "picking-waves",
                "{{waveId}}",
                "cancel"
              ]
            },
            "description": "Calls off the whole walk, and every shipment on it with it. The orders' holds survive: nothing was posted and the customers are still promised their goods.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Power cut\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delivery Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "options"
              ]
            },
            "description": "Consignment states, COD states and the failure reasons. Each reason says whether it is WORTH RETRYING: a wrong address will fail the same way tomorrow and somebody has to talk to the customer first, and damaged goods must never go out again at all."
          }
        },
        {
          "name": "List Deliveries",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries"
              ]
            },
            "description": "The delivery round, oldest promise first and undated last. `open=1` is what is still somebody's problem; `cod_only=1` narrows it to money."
          }
        },
        {
          "name": "Book A Delivery",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries"
              ]
            },
            "description": "Records what is about to happen to goods that have already LEFT (T062), so the shipment has to be dispatched first. A fulfilment is what left the shop; a delivery is what happened to it — a shipment leaves once, and a delivery can be attempted three times, fail twice and come back. Calling this twice returns the SAME consignment: a second attempt is an ATTEMPT, not a second delivery, or \"how many deliveries did we make\" counts the failures. `cod_amount` defaults to what the order still OWES rather than its total — a customer who paid a deposit over the phone must not be asked for the whole thing at the door.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_fulfilment_id\": 1,\n  \"courier_id\": 1,\n  \"is_cod\": true,\n  \"promised_for\": \"2026-09-04\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Delivery",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}"
              ]
            },
            "description": "The consignment with EVERY attempt, in order. \"We went twice and nobody was in\" is the answer to a complaint, and it only exists because each try was kept — the attempts are append-only and refuse an edit or a delete."
          }
        },
        {
          "name": "Send It Out",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}/dispatch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}",
                "dispatch"
              ]
            },
            "description": "The van has left the yard.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"courier_id\": 1,\n  \"tracking_reference\": \"SW-88213\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "It Arrived",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}/delivered",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}",
                "delivered"
              ]
            },
            "description": "On a COD consignment this is also the moment the customer PAYS, and two different things happen. The CUSTOMER has paid: a payment lands on the order as `cod` (never as cash — nothing went in a drawer) and the sale stops showing as unpaid. The SHOP has not been paid: the COD amount becomes \"held by the courier\", which is a debt with somebody's name on it and belongs on no customer ageing report. A COD consignment delivered with no money is REFUSED — the goods only change hands against payment, and handing them over anyway is recorded as a failure to pay. Send an Idempotency-Key: a retry must not take the money twice.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"recipient_name\": \"Nimal Perera\",\n  \"recipient_note\": \"Left at the gate with the security guard\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "An Attempt Failed",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}/failed",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}",
                "failed"
              ]
            },
            "description": "The reason is REQUIRED and comes from a fixed list. Free text would make \"nobody home\", \"no one home\" and \"no answer\" three reasons on every report a shop runs, and the report exists to show which one keeps recurring — each has a different fix. The goods stay WITH THE COURIER: most failures are followed by another attempt tomorrow, and writing the stock back in between would have it bouncing in and out of the ledger.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"nobody_home\",\n  \"notes\": \"Rang twice, no answer\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Try Again",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}/retry",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}",
                "retry"
              ]
            },
            "description": "Back out on the road after a failed attempt.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"promised_for\": \"2026-09-05\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Returned To Sender",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}/return",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}",
                "return"
              ]
            },
            "description": "The goods came back. THIS IS NOT A CUSTOMER RETURN: they never reached the customer, nothing was paid and no tax was charged on a completed sale, so there is nothing to refund. What actually happened is that the DISPATCH was undone — the units go back to the LAYERS they came out of at the cost they left at, the shipment is cancelled and the order is owed again. A DELIVERED consignment that comes back later is refused here, because giving money back is a different act with different tax consequences.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"No such address\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel A Delivery",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/deliveries/{{deliveryId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "deliveries",
                "{{deliveryId}}",
                "cancel"
              ]
            },
            "description": "It never went out.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Customer collected it instead\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Couriers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/couriers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "couriers"
              ]
            },
            "description": "Who carries the goods. The shop's own van is a courier too — it settles the same way, the driver hands the cash in at the end of the round, and treating it as a special case would leave the day's COD takings in nobody's column."
          }
        },
        {
          "name": "Add A Courier",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/couriers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "couriers"
              ]
            },
            "description": "`fee_terms` is a NOTE rather than a rate, and `settlement_days` is display only. The fee is captured onto each settlement because it is negotiated and changes; computing it from a rate held here would restate every settlement ever made the day somebody edited it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"SWIFT\",\n  \"name\": \"Swift Couriers\",\n  \"phone\": \"0112345678\",\n  \"fee_terms\": \"3% of collections, minimum 50\",\n  \"settlement_days\": 7\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Courier",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/couriers/{{courierId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "couriers",
                "{{courierId}}"
              ]
            },
            "description": "Change a courier's details.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"SWIFT\",\n  \"name\": \"Swift Couriers (Pvt) Ltd\",\n  \"status\": \"active\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Retire A Courier",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/couriers/{{courierId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "couriers",
                "{{courierId}}"
              ]
            },
            "description": "Soft-deleted, so past deliveries still name who carried them."
          }
        },
        {
          "name": "Outstanding Cash On Delivery",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/cod/outstanding",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "cod",
                "outstanding"
              ]
            },
            "description": "What every courier is holding, SPLIT FROM RECEIVABLES and split again into TWO COLUMNS (Screen 7.7). Once a courier has collected, the CUSTOMER has paid — they owe nothing and must not appear on an ageing report. But the shop has not been paid either: the courier has the money and owes it. That is a third position, and netting it against either of the other two gives a figure nobody can act on. `to_collect` is still out on the road and may never arrive at all; `held` is a debt with somebody's name on it who can be telephoned. Ordered by who is holding the most."
          }
        },
        {
          "name": "List Settlements",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements"
              ]
            },
            "description": "Couriers handing over what they collected. `open=1` shows the ones not yet posted."
          }
        },
        {
          "name": "Start A Settlement",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements"
              ]
            },
            "description": "Sweeps up every collection the courier is actually HOLDING — a consignment still on the road has not been collected, and settling it would have the courier paying for money nobody has taken yet. `declared_total` is the COURIER'S figure, typed from their statement, and is kept whatever we think: the expected total is ours, and storing only the agreed number would throw away the evidence at exactly the moment somebody needs it. One delivery settles ONCE — two settlements each claiming the same collection is how a courier ends up paying half of what they owe.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"courier_id\": 1,\n  \"branch_id\": 1,\n  \"period_from\": \"2026-09-01\",\n  \"period_to\": \"2026-09-07\",\n  \"declared_total\": \"9000.0000\",\n  \"fee_amount\": \"300.0000\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get A Settlement",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements/{{settlementId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements",
                "{{settlementId}}"
              ]
            },
            "description": "The settlement with every collection on it. Each line CAPTURES the COD amount, so a figure corrected afterwards cannot silently restate what the courier and the shop shook hands on."
          }
        },
        {
          "name": "Add A Collection",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements/{{settlementId}}/collections",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements",
                "{{settlementId}}",
                "collections"
              ]
            },
            "description": "Put one collection on a draft settlement.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"delivery_id\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Take A Collection Off",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements/{{settlementId}}/collections/{{deliveryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements",
                "{{settlementId}}",
                "collections",
                "{{deliveryId}}"
              ]
            },
            "description": "Only while it is still a draft, before anybody has agreed the figures."
          }
        },
        {
          "name": "Reconcile",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements/{{settlementId}}/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements",
                "{{settlementId}}",
                "reconcile"
              ]
            },
            "description": "Somebody has compared the courier's figure against the shop's. A VARIANCE NEEDS A WRITTEN REASON, for the same purpose as a stock adjustment's: a difference nobody explained is indistinguishable from one nobody noticed, and next month there is no way to tell which it was.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"declared_total\": \"9000.0000\",\n  \"variance_reason\": \"One parcel was paid by card at the door\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Post The Settlement",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements/{{settlementId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements",
                "{{settlementId}}",
                "post"
              ]
            },
            "description": "The money arrived, and every collection on this settlement stops being outstanding. REFUSED on an unreconciled settlement: posting one would clear the COD amounts without anybody having compared the figures, which is the one thing this document exists to do. Reconciling and posting are kept apart because agreeing a figure and receiving the money are different events, often days apart. Once posted a settlement is FINAL — one that turns out to be wrong is corrected by another, the same way a posted receipt is.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"payment_method\": \"bank_transfer\",\n  \"payment_reference\": \"TRX-99\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cancel A Settlement",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/courier-settlements/{{settlementId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "courier-settlements",
                "{{settlementId}}",
                "cancel"
              ]
            },
            "description": "Draft or reconciled only. Its collections go back to unsettled — cancelling the paperwork does not cancel the debt.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Raised against the wrong courier\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Hold The Sale",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/hold",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "hold"
              ]
            },
            "description": "Park the cart and free the till for the next customer. Holding reserves nothing: stock is committed at completion, not while somebody wanders the aisles."
          }
        },
        {
          "name": "Resume A Held Sale",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "resume"
              ]
            },
            "description": "Back to draft, ready to take money."
          }
        },
        {
          "name": "Complete The Sale",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/complete",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "complete"
              ]
            },
            "description": "The only endpoint that moves stock or takes money. Stock out, cost captured from the layers ACTUALLY consumed, tax captured at the rate in force, payment recorded — one transaction. Cash over the total gives change; a card over the total does not, because that is a refund waiting to happen. Send `Idempotency-Key`: a timeout followed by a retry must not sell twice.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"payments\": [\n    {\n      \"method\": \"card\",\n      \"amount\": \"500.0000\",\n      \"reference\": \"AUTH-8841\"\n    },\n    {\n      \"method\": \"cash\",\n      \"amount\": \"300.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Complete An Offline Sale",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/complete",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "complete"
              ]
            },
            "description": "`allow_negative` is for SYNC only. A sale that already happened at a disconnected till is a fact, not a request — the goods left the shelf whatever the server believes the balance to be (OFFLINE_SYNC_DESIGN §4.1). An online till must never send this, or the shop quietly oversells.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"allow_negative\": true,\n  \"payments\": [\n    {\n      \"method\": \"cash\",\n      \"amount\": \"500.0000\",\n      \"client_uuid\": \"{{$guid}}\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Void The Sale",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/void",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "void"
              ]
            },
            "description": "Unwinds a completed sale: compensating movements return each unit to the layer it came out of, at the cost it left at. The DOCUMENT stays — deleting it would leave a gap in the invoice sequence and a receipt in the world with nothing behind it. A separate permission from selling, on purpose.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Rang up in error\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "POS - Scan & Search (T044)",
      "description": "What the till's search bar talks to. Two endpoints, and the split matters at a counter: `lookup` answers a SWIPE with one line ready to drop into the cart, `search` answers TYPING with a short list. One endpoint returning sometimes a line and sometimes a list would put \"did that scan work?\" in front of the operator, which is the one question a queue cannot afford.\n\nGated by `pos.operate`, not `sales.view` — reading sales history is not the same authorisation as driving a counter's scanner.",
      "item": [
        {
          "name": "Scan A Code",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/lookup?code=8901234567890&branch_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "lookup"
              ],
              "query": [
                {
                  "key": "code",
                  "value": "8901234567890"
                },
                {
                  "key": "branch_id",
                  "value": "1"
                }
              ]
            },
            "description": "One code in, one line out. Resolves three things a plain `where barcode = ?` does not: a CASE barcode pins a unit, so one scan of an outer books twelve; a WEIGHT-EMBEDDED code (GS1 restricted range, 02 or 20-29) carries its quantity in the digits, as the deli scale prints it; and a full SKU code resolves exactly — a PARTIAL one does not, because silently adding the first SKU that starts with what was typed is how the wrong product ends up on a bill.\n\nA miss is 404 with something to act on, never an empty 200: a till showing nothing is indistinguishable from a till that has not answered yet."
          }
        },
        {
          "name": "Scan A Weight Label",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/lookup?code=2001234012348&branch_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "lookup"
              ],
              "query": [
                {
                  "key": "code",
                  "value": "2001234012348"
                },
                {
                  "key": "branch_id",
                  "value": "1"
                }
              ]
            },
            "description": "`20 | 01234 | 01234 | C` — prefix, item number, five digits of grams, check digit: 1.234 kg. The answer carries `quantity_from_barcode: true` so the screen can say the quantity came off the LABEL rather than from the operator, which is what lets somebody spot a mis-weighed pack."
          }
        },
        {
          "name": "Search By Name Or Code",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/search?q=milk&branch_id=1&limit=20",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "milk"
                },
                {
                  "key": "branch_id",
                  "value": "1"
                },
                {
                  "key": "limit",
                  "value": "20"
                }
              ]
            },
            "description": "The list an operator picks from when a scan misses — the commonest cause being a torn label. Matches product name, SKU code and barcode; capped, because a POS list nobody can read is no faster than no list at all. Under two characters returns nothing rather than half the shop.\n\nEach row reports AVAILABLE, not on hand: stock already promised to somebody else will not fill a bag."
          }
        }
      ]
    },
    {
      "name": "POS - Sessions & Cash (T045)",
      "description": "One cashier, one drawer, one shift. The session is what makes a variance mean anything — \"the till is 170 short\" with no start, no end and nobody's name on it is a rumour, not a finding.\n\nThree permissions, three jobs: `pos.operate` starts a shift, `pos.session.close` counts the drawer, `pos.variance.approve` signs off a difference. Whoever counted a short till must not also be the one who decides it does not matter.",
      "item": [
        {
          "name": "Session Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "options"
              ]
            },
            "description": "Statuses, cash-movement types, and the denominations a count sheet offers per currency — so a cashier ticks off a printed list rather than typing notes from memory. A sheet that omits the 5000 is a sheet where five thousand quietly goes missing."
          }
        },
        {
          "name": "Active Session For A Counter",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/active?counter_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "active"
              ],
              "query": [
                {
                  "key": "counter_id",
                  "value": "1"
                }
              ]
            },
            "description": "What the till asks on load. A counter with no open session cannot take money, and the worst moment to find that out is after a trolley has been rung up. Answers 200 with a null payload when there is none — no session is a normal state, not an error."
          }
        },
        {
          "name": "List Sessions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions?variance=true&from=2026-08-01&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions"
              ],
              "query": [
                {
                  "key": "variance",
                  "value": "true"
                },
                {
                  "key": "from",
                  "value": "2026-08-01"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`variance=true` is the filter a manager actually uses: the drawers that did not agree."
          }
        },
        {
          "name": "Show Session",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}"
              ]
            },
            "description": "The float and closing sheets note by note, the close-out grid, and every cash movement with its reason. While the session is OPEN the expected cash is computed live from the payments and the movements; once it is closed the frozen figure is returned, because a Z report is a record of a moment."
          }
        },
        {
          "name": "Open A Session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions"
              ]
            },
            "description": "Screen 7.1. The float is counted BY DENOMINATION, because that is what makes the close-out variance investigable — \"twenty 500s, and there are nineteen\" points somewhere, \"10,000 expected, 9,830 counted\" does not.\n\nOne open session per counter is a database fact, not a check: `active_counter_id` mirrors the counter while the session is live and is NULL afterwards, and NULLs do not collide in a unique index. Two cashiers opening the same till at the same instant means one of them loses, which is correct.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"counter_id\": 1,\n  \"shift\": \"Morning\",\n  \"counted_by\": \"R. Dias\",\n  \"witnessed_by\": \"M. Fernando\",\n  \"floats\": [\n    {\n      \"currency\": \"LKR\",\n      \"denomination\": \"1000.0000\",\n      \"quantity\": 5\n    },\n    {\n      \"currency\": \"LKR\",\n      \"denomination\": \"500.0000\",\n      \"quantity\": 20\n    },\n    {\n      \"currency\": \"LKR\",\n      \"denomination\": \"100.0000\",\n      \"quantity\": 50\n    },\n    {\n      \"currency\": \"USD\",\n      \"denomination\": \"20.0000\",\n      \"quantity\": 5\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cash In / Out / Drop",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/cash",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "cash"
              ]
            },
            "description": "A DROP is not cash out. The notes go to the safe, so both reduce what should be IN the drawer at close — but a drop is still the shop's money and still this session's takings, and typing it as an outgoing understates the day by however much was dropped.\n\nA reason is never optional: an unexplained hand in the till is exactly what this record exists to prevent.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"drop\",\n  \"amount\": \"50000.0000\",\n  \"reason\": \"Drawer full \\u2014 to the safe\",\n  \"reference\": \"SAFE-0819\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Suspend The Session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/suspend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "suspend"
              ]
            },
            "description": "The cashier has stepped away; the drawer is locked but the session is still theirs. NOT closed — closing means counting, and counting a drawer at every tea break is how variances get invented."
          }
        },
        {
          "name": "Resume The Session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/resume",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "resume"
              ]
            },
            "description": "Back to selling."
          }
        },
        {
          "name": "X Report",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/x-report",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "x-report"
              ]
            },
            "description": "An interim summary that CHANGES NOTHING, and can be asked for any number of times. A cashier who could make the expected total move by looking at it could make a shortfall disappear. The only thing it writes is a count of how often it has been run — a shift where somebody checked eleven times is worth knowing about."
          }
        },
        {
          "name": "Start Counting",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/counting",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "counting"
              ]
            },
            "description": "Stop selling, start counting. Separate from closing because the expected figure has to stand still while somebody counts against it — a sale rung up halfway through a count produces a variance that is real and meaningless. Refused while a held bill is still parked at the till: a stranded cart is one nobody can recall afterwards."
          }
        },
        {
          "name": "Close And Post Z",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "close"
              ]
            },
            "description": "Screen 7.2. Each currency is counted separately — netting a dollar surplus against a rupee shortfall hides both, and netting card against cash is worse, because a card total that agrees is not evidence about the drawer.\n\nVariance is counted MINUS expected and is never typed; a form asking for both invites them to disagree, and the one anybody trusts is the count. A difference needs a reason before Z posts. Below the threshold (`config/pos.php`) the session reconciles itself; above it, it stays CLOSED until a supervisor accepts it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"counts\": [\n    {\n      \"currency\": \"LKR\",\n      \"denomination\": \"1000.0000\",\n      \"quantity\": 120\n    },\n    {\n      \"currency\": \"LKR\",\n      \"denomination\": \"500.0000\",\n      \"quantity\": 96\n    },\n    {\n      \"currency\": \"USD\",\n      \"denomination\": \"20.0000\",\n      \"quantity\": 5\n    }\n  ],\n  \"totals\": [\n    {\n      \"method\": \"card\",\n      \"currency\": \"LKR\",\n      \"counted_total\": \"96400.0000\"\n    }\n  ],\n  \"variance_reason\": \"Short \\u2014 under investigation\",\n  \"counted_by\": \"R. Dias\",\n  \"witnessed_by\": \"M. Fernando\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Reconcile The Variance",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sessions/{{posSessionId}}/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sessions",
                "{{posSessionId}}",
                "reconcile"
              ]
            },
            "description": "A supervisor accepts the difference and the cash goes to the safe. Separate from closing because they are separate people: the cashier goes home at the end of their shift whether or not anybody has decided what to do about a shortfall. Needs `pos.variance.approve`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Counted again with the manager; accepted as a miscount.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Sales - Receipts & Invoices (T047)",
      "description": "A REPRINT RENDERS THE SNAPSHOT, NEVER A RECALCULATION.\n\nD8 requires a document reprinted years later to reproduce exactly as issued. Re-rendering from the live database cannot promise that — the shop moves, a tax registration is corrected, a product is renamed, the template gains a line, and every one of those quietly rewrites a receipt somebody is holding.\n\nSo the first print FREEZES everything the paper says into `payload`: the shop's header as it read that day, the customer, the lines with their captured prices and tax, the payments with the rate applied to each foreign note, the footer. The template version travels with it, so a layout change next year does not reach last year's paper.\n\nThis is the same capture rule as tax rates, exchange rates and recipes — the one the whole system is built on — applied to the piece of paper.",
      "item": [
        {
          "name": "Get The Receipt",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/receipt?kind=sale_receipt",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "receipt"
              ],
              "query": [
                {
                  "key": "kind",
                  "value": "sale_receipt"
                }
              ]
            },
            "description": "Issues the snapshot on the first call and returns the stored one every time afterwards, so a till that prints, jams and prints again cannot end up with two different receipts for one sale.\n\n`kind=sale_receipt` is the 80 mm till roll: branch header, document number, date and time, cashier, counter, lines with quantity AND unit, discounts, the tax summary grouped BY RATE (one tax figure is unverifiable when a basket mixes rates), totals, the payment breakdown with change, and the return policy footer.\n\nA draft is refused: printing one would hand a customer a document for a sale that has not happened."
          }
        },
        {
          "name": "Get The A4 Tax Invoice",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/receipt?kind=tax_invoice",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "receipt"
              ],
              "query": [
                {
                  "key": "kind",
                  "value": "tax_invoice"
                }
              ]
            },
            "description": "`kind=tax_invoice` is the same facts in the shape a tax authority reads: the organisation's legal name, registration and tax id, the customer's details, tax per line, payment terms and bank details. Those last two are on the A4 and not on the till roll, because nobody pays an invoice from a receipt.\n\nFrozen separately from the receipt: they are issued at different moments — the receipt at the counter, the invoice when somebody asks for one — and each has to reproduce as it was issued."
          }
        },
        {
          "name": "Print / Reprint",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/receipt/print",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "receipt",
                "print"
              ]
            },
            "description": "The first call is the issue; every one after it is a REPRINT, and the answer says which and how many copies exist.\n\nA duplicate receipt is a well-known way to walk goods out of a shop twice, so `print_count` is not a statistic — it is the reason a manager can answer \"why does this sale have nine receipts\". Both the issue and every reprint are written to the audit trail.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"kind\": \"sale_receipt\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Sales - Returns & Exchanges (T048)",
      "description": "Screen 7.6. Four rules do the work here, and each is a way a shop loses money or trust.\n\n**A return names the original LINE, not a SKU.** Price, tax rate and cost are copied from what was actually sold — a return priced from today's catalogue would refund whatever the shirt costs this week rather than what the customer paid for it in a sale.\n\n**Tax reverses on the ORIGINAL basis (B5).** If VAT was 15% in August and is 18% now, an August sale reverses 15%: that is what the shop collected and that is what goes back.\n\n**Only SELLABLE goods go back into stock**, at the cost they left at, into the layer they came out of. Quarantine, damaged, warranty and scrap are recorded with their cost as a write-off and post no movement — a damaged bottle booked back in is a damaged bottle somebody will sell.\n\n**You cannot return more than was sold.** `order_lines.quantity_returned` is incremented in SQL, never read-modify-written.\n\n`sales.refund` gates all of it, separately from `sales.create`.",
      "item": [
        {
          "name": "Return Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns",
                "options"
              ]
            },
            "description": "Dispositions with whether each one goes back on the shelf, the refund methods policy allows, and the return window in days — so a screen can say \"this is outside the window\" while somebody is still typing, rather than after they press post."
          }
        },
        {
          "name": "What Can Still Come Back",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns/returnable/{{orderId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns",
                "returnable",
                "{{orderId}}"
              ]
            },
            "description": "The sale's lines with what is LEFT to return on each. A screen offering the sold quantity would let somebody return the same shirt twice, and the second refund is the one nobody notices."
          }
        },
        {
          "name": "List Returns",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns?out_of_policy=true&from=2026-08-01&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns"
              ],
              "query": [
                {
                  "key": "out_of_policy",
                  "value": "true"
                },
                {
                  "key": "from",
                  "value": "2026-08-01"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`out_of_policy=true` and `with_write_off=true` are the two filters a manager actually uses: late returns somebody accepted, and goods that came back unsellable."
          }
        },
        {
          "name": "Show Return",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns/{{salesReturnId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns",
                "{{salesReturnId}}"
              ]
            },
            "description": "Lines with their captured price and tax rate, their disposition, and the layer the sellable ones went back into. `net_cost` is what the shop actually lost: the refund, less what came back as sellable stock, plus what was written off."
          }
        },
        {
          "name": "Raise A Return",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns"
              ]
            },
            "description": "Against a completed sale. Every line names the `order_line_id` it is coming back from, and the disposition decides whether the goods rejoin sellable stock or are written off.\n\nA return outside the window is NOT refused — shops accept late returns with a manager's say-so, and a system that refuses one simply gets worked around. It is flagged, and `within_policy` plus `days_since_sale` are CAPTURED onto the document so changing the window next month does not restate whether this one was in policy.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"order_id\": 1,\n  \"reason_code\": \"wrong_size\",\n  \"refund_method\": \"cash\",\n  \"pos_session_id\": 1,\n  \"lines\": [\n    {\n      \"order_line_id\": 1,\n      \"quantity\": \"1.000000\",\n      \"disposition\": \"sellable\",\n      \"reason_code\": \"wrong_size\"\n    },\n    {\n      \"order_line_id\": 2,\n      \"quantity\": \"1.000000\",\n      \"disposition\": \"damaged\",\n      \"reason_code\": \"damaged_packaging\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Replace The Lines",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns/{{salesReturnId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns",
                "{{salesReturnId}}"
              ]
            },
            "description": "Replaces every line on a DRAFT. A posted return refuses this: the stock has moved and the money has gone back over the counter."
          }
        },
        {
          "name": "Post The Return",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns/{{salesReturnId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns",
                "{{salesReturnId}}",
                "post"
              ]
            },
            "description": "One transaction: stock back where it belongs, money back to the customer. A refund handed over with no stock movement behind it, and stock booked in with no refund, are both worse than nothing happening at all.\n\nSerial-tracked lines are verified against the serials that LEFT on the original sale — accepting any serial is accepting a unit the shop never sold, which is how a warranty claim gets made against somebody else's purchase.\n\nA cash refund at a till is recorded as a cash movement OUT of the drawer, or the close-out would expect money that has gone back over the counter and the cashier would be short by exactly the refunds they processed."
          }
        },
        {
          "name": "Exchange Instead",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/sales-returns/{{salesReturnId}}/exchange",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "sales-returns",
                "{{salesReturnId}}",
                "exchange"
              ]
            },
            "description": "An exchange is a return PLUS a replacement sale, linked by `exchange_order_id` — not a third kind of document. Two documents that each stand on their own is what makes both halves auditable, and the link is what keeps either findable from the other.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 3,\n      \"quantity\": \"1.000000\",\n      \"unit_price\": \"4250.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "POS - Offline Sync (T049)",
      "description": "A COMPLETED SALE IS A FACT, NOT A REQUEST.\n\nThe server does not refuse a synced sale because the stock ran out, the price moved or the session closed. The goods left the shelf and a customer walked out with them; refusing would record a fiction. It ACCEPTS and raises an EXCEPTION for a supervisor — a work queue, not an error log.\n\n`rejected` is deliberately almost unreachable: it is for a payload the server cannot parse, not for anything a shop did. If it starts appearing for business reasons, that is a defect in the design.\n\n`client_uuid` is generated on the till BEFORE the sale is saved locally, and is unique here — so a replay is a `duplicate`, which is a SUCCESS and not an error. A server-generated id could not work: the till has to print a receipt and hand over the goods before the server has heard of the sale.",
      "item": [
        {
          "name": "Bootstrap A Till",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/bootstrap?counter_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "bootstrap"
              ],
              "query": [
                {
                  "key": "counter_id",
                  "value": "1"
                }
              ]
            },
            "description": "Everything a till needs to trade alone: SKUs, barcodes, prices, tax classes, and the rules for what it may do while disconnected.\n\nDeliberately narrow — no cost prices, no full customer records, no other branches' anything. A till is a device that gets stolen, and it should be worth nothing to whoever takes it."
          }
        },
        {
          "name": "Pull Changes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/delta?cursor=2026-08-27 06:00:00&counter_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "delta"
              ],
              "query": [
                {
                  "key": "cursor",
                  "value": "2026-08-27 06:00:00"
                },
                {
                  "key": "counter_id",
                  "value": "1"
                }
              ]
            },
            "description": "What has changed since the cursor. The cursor is the SERVER's watermark, not a clock — a till with a wrong clock is common, and must not be able to miss a price change or replay one. A cursor the server cannot read means a full refresh: slow, but correct, where guessing would silently skip rows."
          }
        },
        {
          "name": "Push A Batch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sync",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sync"
              ]
            },
            "description": "The till's outbox, oldest first. One verdict per transaction:\n\n• `accepted` — recorded cleanly\n• `accepted_with_exception` — recorded; a supervisor must look\n• `duplicate` — already had it. A SUCCESS: the till pushed twice because it could not tell whether the first arrived\n• `rejected` — unparseable\n\nThe batch STOPS at the first rejection. Ordering matters more than throughput: a session open must land before the sales inside it, and skipping past a bad row would break that. A sale references its session by `client_uuid` too, so both halves can arrive in the same batch and still resolve.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"counter_id\": 1,\n  \"transactions\": [\n    {\n      \"client_uuid\": \"{{$guid}}\",\n      \"kind\": \"session_open\",\n      \"occurred_at\": \"2026-08-27T06:12:00+05:30\",\n      \"payload\": {\n        \"counter_id\": 1,\n        \"shift\": \"Morning\",\n        \"floats\": [\n          {\n            \"currency\": \"LKR\",\n            \"denomination\": \"1000.0000\",\n            \"quantity\": 10\n          }\n        ]\n      }\n    },\n    {\n      \"client_uuid\": \"{{$guid}}\",\n      \"kind\": \"sale\",\n      \"occurred_at\": \"2026-08-27T07:41:00+05:30\",\n      \"payload\": {\n        \"lines\": [\n          {\n            \"product_variant_id\": 1,\n            \"quantity\": \"2.000000\",\n            \"unit_price\": \"150.0000\"\n          }\n        ],\n        \"payments\": [\n          {\n            \"method\": \"cash\",\n            \"amount\": \"300.0000\",\n            \"client_uuid\": \"{{$guid}}\"\n          }\n        ]\n      }\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Sync Status",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/sync/status?counter_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "sync",
                "status"
              ],
              "query": [
                {
                  "key": "counter_id",
                  "value": "1"
                }
              ]
            },
            "description": "What the server has heard from this counter: the cursor, the last push, how many transactions were rejected and how many exceptions are still open. The till shows the last two on screen — D6 requires the pending count to be visible at all times, never a silent failure."
          }
        },
        {
          "name": "Exception Kinds",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/exceptions/kinds",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "exceptions",
                "kinds"
              ]
            },
            "description": "The six things that can disagree, each with what somebody can actually DO about it. An exception nobody can act on should not be raised at all."
          }
        },
        {
          "name": "The Supervisor's Queue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/exceptions?status=open&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "exceptions"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "open"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "Open by default. Every entry concerns a sale that was ACCEPTED — the goods left the shelf and the customer went home — so this is a work list, not a list of failures."
          }
        },
        {
          "name": "Close An Exception",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/exceptions/{{exceptionId}}/resolve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "exceptions",
                "{{exceptionId}}",
                "resolve"
              ]
            },
            "description": "Somebody has dealt with it: accepted as it stands, stock corrected, customer contacted, or no action needed. Audited, because \"who decided the till was allowed to be 4,000 short\" has to be answerable.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"resolution\": \"adjusted\",\n  \"note\": \"Counted the shelf; 3 had been received and not booked in.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "POS - Till PINs (T049)",
      "description": "A PIN unlocks a SESSION at a till; it does NOT grant a token.\n\nThat distinction is the whole security position. A till cannot reach the auth server when the line is down, but a shift change still has to work — so the counter caches the operators permitted at its branch with their PIN hashes, and six digits unlock the till locally. Anything needing real authority (a card refund, a price override above a threshold, anything touching another branch) is REFUSED offline rather than authorised by a PIN.\n\nTwo things keep a cached six-digit hash honest: it is bcrypt at the same cost as a password, so each guess is expensive; and the cache expires after `pos.offline_window_hours` — 72 by default — so a stolen till stops being useful on Monday.\n\nThe digits never appear in a response, a log or an audit entry.",
      "item": [
        {
          "name": "Set My Till PIN",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/pin",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "pin"
              ]
            },
            "description": "Your own, and it needs your password — changing a PIN proves you are the person, the same way changing a password does. A till left unlocked is otherwise a till whose PIN anybody can replace.\n\nObvious PINs are refused: repeated digits, simple runs, and the handful at the top of every leaked-PIN list. A shop that lets an operator choose 123456 has a PIN that protects nothing.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"pin\": \"480216\",\n  \"current_password\": \"password\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Clear My Till PIN",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/pin",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "pin"
              ]
            },
            "description": "Stop working tills. The cached hash goes on the next bootstrap."
          }
        },
        {
          "name": "Verify A PIN",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/pin/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "pin",
                "verify"
              ]
            },
            "description": "For a till that IS connected: the same decision made in the one place that can also record it. The answer says nothing about which half was wrong.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"user_id\": 2,\n  \"pin\": \"480216\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Operators A Till May Cache",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/pos/pin/operators?counter_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "pos",
                "pin",
                "operators"
              ],
              "query": [
                {
                  "key": "counter_id",
                  "value": "1"
                }
              ]
            },
            "description": "Returns HASHES, scoped to the counter's branch — a till in Colombo has no business holding the PIN hashes of everybody in Kandy, and a stolen device should give up as little as possible.\n\nOnly people who can actually operate a till, and only those with a PIN set: caching a row nobody can unlock is a hash handed over for nothing. The permission list is trimmed to what a till acts on, because the full set would tell whoever took the device how the business is organised."
          }
        }
      ]
    },
    {
      "name": "Pricing - Promotions (T053)",
      "description": "B7's evaluation order, exactly, in one engine:\n\n3. Product and line promotions\n4. Bill-level promotions\n5. Customer benefits\n6. Coupons (T054 presents them; they evaluate here)\n7. Stacking and exclusion — applied THROUGHOUT, not as a late pass\n8. Manual discount, which is the caller's\n\n**Step 7 is deliberately not a separate stage.** A stacking rule that ran at the end would have to UNDO discounts already calculated, and \"undo\" is where rounding errors and off-by-one refunds come from. The engine checks whether each promotion is allowed BEFORE applying it, so nothing is ever taken back.\n\n**Priority and stacking are FIELDS, not behaviour.** A shop running four overlapping offers has to see which wins and whether they combine without reading the engine. `stackable: false` blocks others at the same level; `exclusive: true` blocks everything.\n\n**The order is TOTAL:** priority, then id. Two offers that tie would otherwise discount differently on two servers, and the difference would surface as a customer comparing receipts.\n\n**Nothing goes negative.** Every discount is capped at what is left on the line: two stacked offers must not turn a sale into a refund.",
      "item": [
        {
          "name": "Promotion Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "options"
              ]
            },
            "description": "Scopes with their B7 step number, reward types with whether the value is a percentage and whether a free SKU is needed, target types, and the day-of-week bitmask (Monday = 1, so a weekend offer is 96)."
          }
        },
        {
          "name": "Lookup Targets",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/lookup?type=category&search=groc",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "lookup"
              ],
              "query": [
                {
                  "key": "type",
                  "value": "category"
                },
                {
                  "key": "search",
                  "value": "groc"
                }
              ]
            },
            "description": "Names what an offer covers — `variant`, `product`, `category` or `brand`.\n\nIts own endpoint behind `promotions.view` rather than the catalogue's, because a builder that needs `catalog.view` to pick a category is a builder half the people who run promotions cannot use. It answers with LABELS only — no prices, no costs — so it grants nothing the promotion screen does not already show.\n\nScreen 8.3 uses it twice: for the target chips, and for the Test basket."
          }
        },
        {
          "name": "List Promotions",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions?status=active&on=2026-08-27&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "active"
                },
                {
                  "key": "on",
                  "value": "2026-08-27"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "Ordered the way the ENGINE runs: line, then bill, then customer; within each, priority then id. The list reads as the evaluation order, so a shop can see what will happen without simulating it."
          }
        },
        {
          "name": "Show A Promotion",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/{{promotionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "{{promotionId}}"
              ]
            },
            "description": "With its targets, each carrying the LABEL of what it covers rather than only an id — reopening an offer is exactly when somebody needs to confirm what it covers, and a chip reading \"variant #1\" answers nothing. A target whose row has since been deleted comes back with a null label.\n\nAn empty target list means the WHOLE SHOP — a \"10% off everything\" offer should not need seven thousand rows."
          }
        },
        {
          "name": "Create A Promotion",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions"
              ]
            },
            "description": "Starts as a DRAFT, and a draft discounts nothing.\n\n`targets` is what it covers: `variant`, `product`, `category` or `brand`, with `is_exclusion: true` for \"everything in Grocery EXCEPT baby formula\" — the alternative is listing four thousand SKUs, which nobody does, which means the offer runs on the formula too.\n\n`max_discount` matters more than it looks: a percentage with no ceiling on a wholesale basket is how a shop gives away its month.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"FESTIVE-10\",\n  \"name\": \"Festive 10% off groceries\",\n  \"scope\": \"line\",\n  \"starts_on\": \"2026-11-01\",\n  \"ends_on\": \"2026-12-31\",\n  \"min_quantity\": \"3.000000\",\n  \"reward_type\": \"percent\",\n  \"reward_value\": \"10.0000\",\n  \"max_discount\": \"2000.0000\",\n  \"priority\": 10,\n  \"stackable\": true,\n  \"exclusive\": false,\n  \"targets\": [\n    {\n      \"target_type\": \"category\",\n      \"target_id\": 4\n    },\n    {\n      \"target_type\": \"product\",\n      \"target_id\": 91,\n      \"is_exclusion\": true\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Promotion",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/{{promotionId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "{{promotionId}}"
              ]
            },
            "description": "A DRAFT or a PAUSED promotion only. A running one refuses it: two baskets ninety seconds apart must not get different offers with no record of why. Pause it, change it, start it again."
          }
        },
        {
          "name": "Start It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/{{promotionId}}/activate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "{{promotionId}}",
                "activate"
              ]
            },
            "description": "From here it discounts real baskets, so the reward has to be complete — a free-item offer with no SKU named would qualify, give nothing, and look exactly like a broken promotion.\n\nNeeds `promotions.approve`."
          }
        },
        {
          "name": "Pause It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/{{promotionId}}/pause",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "{{promotionId}}",
                "pause"
              ]
            },
            "description": "Stop it NOW, keep the setup. What somebody wants at 11am when an offer turns out to be giving away more than they meant — deleting would lose both the configuration and the record of what it already gave away."
          }
        },
        {
          "name": "End It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/{{promotionId}}/expire",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "{{promotionId}}",
                "expire"
              ]
            },
            "description": "Over. The record of what it gave away stays, because \"what did that offer cost us\" is a question a business asks every month."
          }
        },
        {
          "name": "Test Against A Basket",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/promotions/test",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "promotions",
                "test"
              ]
            },
            "description": "Screen 8.3's Test button. Answers with what applied, what did NOT and WHY — the second half is what a shop actually asks about. \"Needs 3 of the qualifying items; the bill has 2\" is a usable answer; a silent non-discount is not.\n\nLines with no `unit_price` are priced through the T052 resolver, so the test runs against what a till would actually charge.\n\n`at` lets a lunchtime or weekend offer be tested at the moment it would run, rather than only at the moment somebody happens to press the button.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"4.000000\"\n    },\n    {\n      \"product_variant_id\": 2,\n      \"quantity\": \"1.000000\"\n    }\n  ],\n  \"channel\": \"pos\",\n  \"branch_id\": 1,\n  \"at\": \"2026-11-15T12:30:00+05:30\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Pricing - Price Lists (T052)",
      "description": "Step ONE of B7's ten-step evaluation order: determine the eligible base price. Everything after it — promotions, coupons, manual discounts — starts from whatever `resolve` returns, so it has to be right and it has to be the same on every server.\n\n**A price is EFFECTIVE-DATED and a transaction keeps a SNAPSHOT.** Both halves matter: next month's prices load today and take over on the day, and every sale already made keeps the price it was made at.\n\n**The same product has more than one price.** Rice at 620 per kg and 14,200 per case of 24 are two prices, not one divided by 24 — a case is cheaper per kilo, which is why anybody buys one. So the unit is part of the key.\n\n**Quantity breaks are rows, not a formula.** \"10 or more at 4,050\" is another row with `min_quantity: 10`; a shop cannot check a formula against a shelf-edge label.\n\n**Which list wins is decided, not discovered:** specificity, then priority, then the later start date, then the id. The last one makes the order TOTAL — a rule that can tie is a rule that prices differently on two servers, and nobody would ever find out why.",
      "item": [
        {
          "name": "Price List Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "options"
              ]
            },
            "description": "Channels and statuses, so a screen never hard-codes an enum the API owns."
          }
        },
        {
          "name": "List Price Lists",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists?status=active&on=2026-08-27&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "active"
                },
                {
                  "key": "on",
                  "value": "2026-08-27"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "`on=` is Screen 8.1's \"active on date\" filter: what the shop charges on a given day, which is not the same as what is not archived. A list that ended in November still explains a November receipt."
          }
        },
        {
          "name": "Show A Price List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/{{priceListId}}?search=shirt",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "{{priceListId}}"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "shirt"
                }
              ]
            },
            "description": "Screen 8.2. Every entry with its unit, its quantity break, and the margin against the SKU's cost — null when there is no cost, because \"we do not know\" and \"we make nothing\" are different answers a buyer has to tell apart."
          }
        },
        {
          "name": "Create A Price List",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists"
              ]
            },
            "description": "Starts as a DRAFT, and a draft is invisible to the resolver — a half-entered list must never price a sale.\n\nLeave `channel`, `branch_id` or `customer_group_id` out and the list applies to every one of them; that is what a default list is. Naming one makes the list more SPECIFIC, and specificity is the first thing the resolver sorts on.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"RETAIL-2026\",\n  \"name\": \"Retail 2026\",\n  \"currency\": \"LKR\",\n  \"tax_inclusive\": true,\n  \"effective_from\": \"2026-01-01\",\n  \"entries\": [\n    {\n      \"product_variant_id\": 1,\n      \"unit_price\": \"4250.0000\"\n    },\n    {\n      \"product_variant_id\": 1,\n      \"unit_price\": \"4050.0000\",\n      \"min_quantity\": \"10.000000\"\n    },\n    {\n      \"product_variant_id\": 2,\n      \"unit_price\": \"620.0000\"\n    },\n    {\n      \"product_variant_id\": 2,\n      \"uom_id\": 12,\n      \"unit_price\": \"14200.0000\",\n      \"notes\": \"Case of 24 \\u2014 cheaper per kilo, which is the point\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Price List",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/{{priceListId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "{{priceListId}}"
              ]
            },
            "description": "The list's own settings — name, scope, dates, priority. A DRAFT only; an active list refuses it for the same reason its prices are frozen.\n\n`code` is prohibited here on purpose: it is how every sale made at these prices refers to the list, and renaming that reference breaks the trail backwards.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Retail 2026 (revised)\",\n  \"channel\": \"pos\",\n  \"branch_id\": 1,\n  \"effective_from\": \"2026-02-01\",\n  \"priority\": 10\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Replace The Prices",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/{{priceListId}}/entries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "{{priceListId}}",
                "entries"
              ]
            },
            "description": "Replaces every entry on a DRAFT. An ACTIVE list refuses this: prices change by SUPERSEDING — a new list dated later — because editing in place would let a basket priced at 10:00 and paid at 10:02 disagree with itself, and there would be no record of what it used to say.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"entries\": [\n    {\n      \"product_variant_id\": 1,\n      \"unit_price\": \"4500.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Activate It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/{{priceListId}}/activate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "{{priceListId}}",
                "activate"
              ]
            },
            "description": "Publishing commits the shop to these numbers, so it needs `pricing.approve` — a different code from `pricing.update`, and usually a different person.\n\nAn EMPTY list is refused: it would match nothing and quietly do nothing, which looks exactly like a list that is not working."
          }
        },
        {
          "name": "Copy It For Next Year",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/{{priceListId}}/duplicate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "{{priceListId}}",
                "duplicate"
              ]
            },
            "description": "The commonest way a shop makes next year's prices. `adjust_percent` raises every entry at once — doing that by hand across seven thousand SKUs is how a digit goes missing.\n\nThe copy is always a DRAFT, whatever the source was: a list nobody has looked at must not start pricing sales.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"RETAIL-2027\",\n  \"name\": \"Retail 2027\",\n  \"effective_from\": \"2027-01-01\",\n  \"adjust_percent\": \"7.5\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Archive It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/{{priceListId}}/archive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "{{priceListId}}",
                "archive"
              ]
            },
            "description": "Archived rather than deleted: every sale made at these prices points here, and a shop asked to justify last quarter's margin needs to be able to read it."
          }
        },
        {
          "name": "What Would This Cost?",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/price-lists/resolve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "price-lists",
                "resolve"
              ]
            },
            "description": "The question a shop floor asks several times a week, answered with the REASON. \"Retail 2026, 10 or more\" is what somebody at a counter needs when a customer asks why; a number on its own is not.\n\n`is_from_list: false` means no list covered the SKU and its own price was used — the designed behaviour, not an error. Most shops price a few thousand SKUs and leave the rest on the product.\n\nThe whole cart resolves in ONE pass: the candidate lists depend on the context, not the SKU.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"product_variant_id\": 1,\n      \"quantity\": \"12.000000\"\n    },\n    {\n      \"product_variant_id\": 2,\n      \"quantity\": \"1.000000\",\n      \"uom_id\": 12\n    }\n  ],\n  \"channel\": \"pos\",\n  \"branch_id\": 1\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Pricing - Coupons (T054)",
      "description": "B7 step 6. A coupon does NOT know what it is worth: it names a PROMOTION and nothing else. The promotion says what the discount is, what it covers, when it runs and how it stacks; the coupon says who may have it and how often. Giving a coupon its own reward fields would be a second discount engine evaluated in a different order, and the day the two disagreed nobody could say which was right.\n\n**A code unlocks its OWN offer.** Presenting a voucher for 10% off coffee must not also fire the staff discount and the clearance sale, so the engine resolves each presented code to the promotion behind it rather than treating any code as permission for every coupon-gated offer.\n\n**Redeeming is ONE conditional UPDATE:**\n\n    UPDATE coupons SET usage_count = usage_count + 1\n    WHERE id = ? AND (usage_limit IS NULL OR usage_count < usage_limit)\n\nrefused when it matched no rows. Two tills presenting the last use of a single-use voucher both read 0 and both write 1 — the same lost update as `stock_balances`, except what it corrupts is a promise made to one named customer.\n\n**Checking is not spending.** `validate` takes nothing. The use is taken when the SALE completes, and given back when it is voided — the customer still holds the voucher and still has not had the discount.\n\n**`usage_limit: null` means UNLIMITED**, and is read with `array_key_exists` rather than `??`, or an explicit null silently becomes single-use.",
      "item": [
        {
          "name": "Check A Code",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons/validate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons",
                "validate"
              ]
            },
            "description": "What the TILL calls while the customer is standing there. Takes nothing — a check that consumed a use would burn a voucher for anybody who typed a code and then changed their mind.\n\nAnswers with the REASON when it will not work. \"That code is not valid\" is the most annoying thing a counter can say to somebody holding a voucher: \"it expired on the 4th\" and \"it starts on Friday\" are things they can act on.\n\nAn unknown code and a withdrawn one read the same on purpose — telling somebody which codes EXIST is how a code space gets enumerated.\n\nBehind `coupons.view` OR `pos.operate`, because the person who needs it is usually a cashier.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"SAVE10\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Coupons",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons?promotion_id=1&state=usable&per_page=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons"
              ],
              "query": [
                {
                  "key": "promotion_id",
                  "value": "1"
                },
                {
                  "key": "state",
                  "value": "usable"
                },
                {
                  "key": "per_page",
                  "value": "50"
                }
              ]
            },
            "description": "`state` is the filter a shop actually wants, and it is NOT the `status` column: a code can be `active` and dead. `usable` means active, in date and with uses left; `expired` and `exhausted` are the two ways it stops being so."
          }
        },
        {
          "name": "Show A Coupon",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons/{{couponId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons",
                "{{couponId}}"
              ]
            },
            "description": "With its redemptions — which sale used it, when, and what it took off. \"Did that mailer work?\" is the question this answers."
          }
        },
        {
          "name": "Issue One Coupon",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons"
              ]
            },
            "description": "A single named code, e.g. one posted to a customer who complained.\n\nLeave `code` out and one is generated from a deliberately reduced alphabet: no O or 0, no I, 1 or L. These get read down a phone and copied off paper, and every one of those pairs is a support call.\n\n`usage_limit` defaults to 1. Passing it explicitly as **null** means unlimited.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"promotion_id\": 1,\n  \"code\": \"SORRY-ABOUT-THAT\",\n  \"usage_limit\": 1,\n  \"expires_on\": \"2026-12-31\",\n  \"notes\": \"Goodwill after the delivery went missing\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Generate A Batch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons/generate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons",
                "generate"
              ]
            },
            "description": "\"500 codes for the December mailer.\"\n\nUniqueness is the UNIQUE INDEX's job, not the generator's: a generator that SELECTs to check and then INSERTs races itself the moment two people press the button together. Collisions are dropped and regenerated.\n\nNeeds `coupons.generate`, which is separate from `coupons.view` — a batch of codes is money the shop has promised to give away.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"promotion_id\": 1,\n  \"name\": \"December mailer\",\n  \"quantity\": 500,\n  \"prefix\": \"DEC\",\n  \"usage_limit_each\": 1,\n  \"expires_on\": \"2026-12-31\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Batches",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupon-batches?promotion_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupon-batches"
              ],
              "query": [
                {
                  "key": "promotion_id",
                  "value": "1"
                }
              ]
            },
            "description": "One row per issuance, with how many codes it holds."
          }
        },
        {
          "name": "Withdraw A Whole Batch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupon-batches/{{batchId}}/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupon-batches",
                "{{batchId}}",
                "disable"
              ]
            },
            "description": "What a shop wants the morning a mailer's codes turn up on a deals forum: ONE action, not five hundred.\n\nCodes already redeemed keep their redemptions — the discount was given and the record of it is the point."
          }
        },
        {
          "name": "Withdraw One Coupon",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons/{{couponId}}/disable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons",
                "{{couponId}}",
                "disable"
              ]
            },
            "description": "Stops it being accepted. The redemptions it already has are history and stay."
          }
        },
        {
          "name": "Reinstate A Coupon",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/coupons/{{couponId}}/enable",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "coupons",
                "{{couponId}}",
                "enable"
              ]
            },
            "description": "Undoes a withdrawal. Does NOT revive an expired code — expiry is a DATE, not a status, because a status would need a nightly job to stay true and the day it did not run every dead voucher would work again."
          }
        },
        {
          "name": "Present A Coupon On A Cart",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/coupon",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "coupon"
              ]
            },
            "description": "The code is checked BEFORE it goes on the order, so a counter that mistypes it hears why instead of watching the total not change and having to guess.\n\nApplying it RE-PRICES the whole basket rather than adjusting a total: a coupon unlocks a promotion, and a promotion is evaluated against the basket as a whole (B7). The codes are kept on the SALE, so changing the cart afterwards re-evaluates with the voucher still in hand.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"SAVE10\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Take A Coupon Off",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/orders/{{orderId}}/coupon/{{couponCode}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "orders",
                "{{orderId}}",
                "coupon",
                "SAVE10"
              ]
            },
            "description": "Re-prices without it. Lines the ENGINE added — a free item from a \"buy 2 get 1\" — are left out of the rebuild and regenerated, or two rechecks would give the customer three free bottles."
          }
        }
      ]
    },
    {
      "name": "Customers (T055)",
      "description": "Screens 9.1-9.2. Customers are CHAIN-WIDE: somebody who shops at two branches is one person, and a per-branch register would give them two credit limits (T056) and two store-credit balances (T057) — which is the one thing a shared balance must never have.\n\n**CONSENT IS A HISTORY, NOT A FLAG.** `marketing_consent = true` cannot answer the only questions that matter when somebody complains: WHEN did they agree, and HOW? It also cannot tell \"never asked\" from \"asked and refused\", and it is silently overwritten the next time an importer runs. So `customer_consents` is APPEND-ONLY — one row per decision per channel per purpose, each carrying its source — and the model refuses an update or a delete. A change of mind is a NEW row.\n\nThe default is NO. Silence is not consent: a customer nobody ever asked has not agreed to anything, and treating a missing row as permission is how a register full of imported contacts becomes a mailing list nobody opted into.\n\n`purpose` keeps marketing apart from SERVICE. \"Your order is ready\" is not marketing, and a shop that stops sending it because somebody unsubscribed from offers has broken the thing the customer actually wanted.\n\n**A MERGE IS NOT A DELETE.** It MOVES the sales, addresses and consent onto the survivor; the loser is kept, retired and pointing at the winner, so an old link or a printed receipt still resolves to the right person. Where the two records DISAGREE about consent, the more RESTRICTIVE position wins — letting a \"yes\" win because it carries a later timestamp uses a filing accident as permission to contact somebody who said no.",
      "item": [
        {
          "name": "List Customers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers?search=0771234567&status=active&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers"
              ],
              "query": [
                {
                  "key": "search",
                  "value": "0771234567"
                },
                {
                  "key": "status",
                  "value": "active"
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            },
            "description": "Search matches name, code, email and PHONE — phone on digits only, because the same number gets written 077-123-4567, 0771234567 and +94 77 123 4567 by three different people and a counter has whichever is on the card in front of them.\n\nRecords that were merged away are HIDDEN unless `merged=1`: they exist so old links resolve, not so somebody picks one at a counter."
          }
        },
        {
          "name": "Show A Customer",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}"
              ]
            },
            "description": "With addresses and the current consent position per channel."
          }
        },
        {
          "name": "Register A Customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers"
              ]
            },
            "description": "`code` is generated when it is left out — sequential and readable, because it gets read down a phone.\n\nConsent given at the counter can be recorded here, WITH its source. It is never assumed: a registration form with a pre-ticked box records permission nobody gave.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Sunil Silva\",\n  \"type\": \"individual\",\n  \"mobile\": \"077 765 4321\",\n  \"email\": \"sunil@example.lk\",\n  \"customer_group_id\": 1,\n  \"addresses\": [\n    {\n      \"label\": \"Home\",\n      \"line1\": \"4 Temple Road\",\n      \"city\": \"Kandy\",\n      \"directions\": \"Blue gate opposite the pharmacy\"\n    }\n  ],\n  \"consents\": [\n    {\n      \"channel\": \"sms\",\n      \"granted\": true,\n      \"source\": \"in_store\",\n      \"evidence\": \"Signed the form at the till\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Customer",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}"
              ]
            },
            "description": "A record that LOST a merge refuses this: it is kept only so old links resolve, and the surviving record is somewhere else.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Sunil Silva\",\n  \"mobile\": \"0777654321\",\n  \"customer_group_id\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Retire A Customer",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}"
              ]
            },
            "description": "Retires, never erases. Their SALES stay — a customer who has bought something is part of the record of what the shop sold, and deleting them would leave those sales pointing at nothing."
          }
        },
        {
          "name": "List Addresses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/addresses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "addresses"
              ]
            },
            "description": "Its own rows rather than columns on the customer: a person has a home and a work address, and one set of columns forces whoever takes the second order to overwrite the first one's."
          }
        },
        {
          "name": "Add An Address",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/addresses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "addresses"
              ]
            },
            "description": "The FIRST address is the default whatever the form said — an address nobody has marked is still the only one.\n\n`directions` matters more than it looks: a delivery without it is a driver on the phone.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label\": \"Office\",\n  \"line1\": \"9 Union Place\",\n  \"city\": \"Colombo 02\",\n  \"contact_name\": \"Reception\",\n  \"contact_phone\": \"0112345678\",\n  \"is_default_delivery\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update An Address",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/addresses/{{addressId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "addresses",
                "{{addressId}}"
              ]
            },
            "description": "Setting `is_default_delivery` unmakes every OTHER address in one UPDATE. Read-then-write would let two people editing the same customer both end up default, and a delivery would go to whichever the query happened to return first.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"is_default_delivery\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Remove An Address",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/addresses/{{addressId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "addresses",
                "{{addressId}}"
              ]
            },
            "description": "Soft delete. Phase 6 captures a SNAPSHOT of the address onto each order, so removing one here cannot change where a past delivery went."
          }
        },
        {
          "name": "Consent History",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/consents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "consents"
              ]
            },
            "description": "The POSITION per channel, and the HISTORY behind it. A regulator asks when agreement was given and how it was obtained, and a boolean answers neither.\n\n`marketing` and `service` are reported separately because a marketing opt-out does not silence \"your order is ready\"."
          }
        },
        {
          "name": "Record A Decision",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/consents",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "consents"
              ]
            },
            "description": "Appends. Never edits — the model refuses an update, because the history IS the evidence.\n\n`granted` is REQUIRED and has no default: \"not stated\" is not a decision, and a default would put words in somebody's mouth.\n\n`source` is how it was obtained. \"They ticked a box\" and \"they said yes on the phone\" are different evidence, and a regulator asks which.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"sms\",\n  \"purpose\": \"marketing\",\n  \"granted\": false,\n  \"source\": \"phone\",\n  \"evidence\": \"Asked to be taken off the list\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Possible Duplicates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/duplicates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "duplicates"
              ]
            },
            "description": "Matched on a shared phone number or email, never on a fuzzy NAME — \"A. Perera\" matches four hundred rows in any Sri Lankan register and none of them are duplicates."
          }
        },
        {
          "name": "Merge Into This Customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/merge",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "merge"
              ]
            },
            "description": "The customer in the URL **SURVIVES**; `loser_id` is merged into them.\n\nTheir sales and addresses move across. Inherited addresses arrive NOT default, because one must not silently take over where a delivery goes.\n\nWhere the two records disagree about consent, the more RESTRICTIVE position is written onto the survivor, with an evidence line saying why — so nobody later reads it as the customer changing their mind.\n\nNeeds `customers.merge`, which is its own permission: moving one person's sales onto another record is not the same act as editing a phone number. Audited, because \"why does this customer have an order they do not remember placing\" is a question a shop will be asked.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"loser_id\": 42,\n  \"reason\": \"Same person, registered twice at two branches\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "List Groups",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-groups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-groups"
              ]
            },
            "description": "A group is a LABEL, not a discount. Price lists (T052) and promotions (T053) already scope themselves by `customer_group_id`; a \"default discount %\" here would be a third discount engine running in no defined order against the other two."
          }
        },
        {
          "name": "Create A Group",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-groups",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-groups"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"WHOLESALE\",\n  \"name\": \"Wholesale\",\n  \"description\": \"Trade customers buying by the case\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Group",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-groups/{{groupId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-groups",
                "{{groupId}}"
              ]
            },
            "description": "",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Wholesale & trade\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete A Group",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-groups/{{groupId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-groups",
                "{{groupId}}"
              ]
            },
            "description": "Its customers are UNCHANGED — they simply stop belonging to a group. A group is a label, and removing a label must not remove what it was on."
          }
        }
      ]
    },
    {
      "name": "Customers - Credit Control (T056)",
      "description": "Screen 9.3. The mirror image of the supplier subledger (T028), deliberately: signed entries, direction carried by the type, a balance PROJECTION kept by SQL increment and proved against a SUM, and receipts applied oldest-first. Two sets of ageing arithmetic in one system eventually disagree, and the day they do nobody can say which is right.\n\n**A NULL CREDIT LIMIT MEANS NO CREDIT** — deliberately the opposite of `coupons.usage_limit`, where null means unlimited. A customer nobody has given an account to must not have one, and the safe answer has to be the one you get by leaving the field alone. \"Unlimited credit\" is not modelled at all: every account has a ceiling, and selling beyond it is a per-sale decision somebody signs for.\n\n**TERMS ARE CAPTURED ONTO THE ENTRY.** `due_date` is resolved when the entry is written. Changing a customer from 30 days to 14 must not reach back and make last month's invoices overdue — the shop agreed 30 days on those, and an ageing report that moves under somebody is worse than no ageing report.\n\n**ONLY INVOICES AGE.** Receipts and credit notes reduce the total but belong to no bucket, so they are applied OLDEST FIRST against the aged amounts — the convention the customer's own statement will use. Leaving them unallocated produces buckets that add up to more than the balance.\n\n**ON ACCOUNT IS NOT A TENDER.** Nothing goes in the drawer and no change comes out; it is a DEBT. It is refused OFFLINE for the same reason as store credit: two disconnected tills each seeing 50,000 of headroom will each sell into it, and unlike stock there is no physical limit on the oversell.",
      "item": [
        {
          "name": "Credit Position",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/credit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "credit"
              ]
            },
            "description": "Limit, terms, balance, what is left, and the ageing. `has_account` is false when no limit has been set — which is not the same as a limit of zero, and the screen has to say which.\n\n`refusal` is what a counter needs BEFORE it offers \"on account\" at all: \"no credit account\" and \"on credit hold\" are different problems and only one of them is fixed by taking a payment."
          }
        },
        {
          "name": "Set Limit And Terms",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/credit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "credit"
              ]
            },
            "description": "`credit_limit: null` removes the account. `credit_hold` stops further account sales WITHOUT touching the limit — what a shop wants when somebody stops paying, because zeroing the limit loses what was agreed and it has to be guessed at when they resume.\n\nNeeds `customers.credit.manage`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"credit_limit\": \"50000.0000\",\n  \"credit_terms_days\": 30,\n  \"credit_hold\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Statement",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/statement?from=2026-06-01&to=2026-08-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "statement"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-06-01"
                },
                {
                  "key": "to",
                  "value": "2026-08-31"
                }
              ]
            },
            "description": "Opening balance, what moved, closing balance, and the ageing — Screen 9.3.\n\nThe opening balance is SUMMED from everything before the window rather than stored: a stored one would have to be maintained per period and would drift the first time an entry was backdated.\n\nEvery line carries a `running_balance`, which is the figure a customer checks against their own records."
          }
        },
        {
          "name": "Ageing Across Customers",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-credit/ageing?as_at=2026-08-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-credit",
                "ageing"
              ],
              "query": [
                {
                  "key": "as_at",
                  "value": "2026-08-31"
                }
              ]
            },
            "description": "Who owes us what, and how late. Customers with a zero balance are left out unless one is asked for by name — an ageing report is a working list, not a directory."
          }
        },
        {
          "name": "Prove The Balance",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/credit/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "credit",
                "reconcile"
              ]
            },
            "description": "The projection against a SUM of the entries. A figure nobody can check is a figure nobody should trust — the same reasoning as `inventory:reconcile`, and the same reason `customers.balance` is maintained by `balance = balance + ?` rather than read-modify-write."
          }
        },
        {
          "name": "Record An Entry By Hand",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/ledger",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "ledger"
              ]
            },
            "description": "The manual door: an opening balance at go-live, a correction, a credit note, a WRITE-OFF.\n\nA write-off is the shop deciding to stop chasing money — an accounting event with tax consequences, not a correction — so it is its own type, needs a REASON, and carries its own permission. \"How much did we write off last year\" is a question somebody will ask.\n\nAn `adjustment` is the one type that keeps the sign it is given, because that is what an adjustment is.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"entry_type\": \"write_off\",\n  \"amount\": \"1250.0000\",\n  \"notes\": \"Gone out of business; receiver appointed\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Outstanding Invoices",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/outstanding",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "outstanding"
              ]
            },
            "description": "What the allocation grid is built from, OLDEST FIRST — the order the customer's own statement will use. Each line carries what is still owed on it after any earlier receipts."
          }
        },
        {
          "name": "List Receipts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/receipts?status=posted",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "receipts"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "posted"
                }
              ]
            },
            "description": ""
          }
        },
        {
          "name": "Take A Receipt",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/receipts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "receipts"
              ]
            },
            "description": "Saved as a DRAFT with its allocations. Nothing moves until it is posted.\n\nAn allocation cannot exceed what is still owed on that invoice, and a receipt cannot be put against another receipt — that would double-count the settlement.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"15000.0000\",\n  \"method\": \"bank_transfer\",\n  \"payment_reference\": \"NEFT 88213\",\n  \"received_on\": \"2026-08-28\",\n  \"allocations\": [\n    {\n      \"customer_ledger_entry_id\": 12,\n      \"amount\": \"10000.0000\"\n    },\n    {\n      \"customer_ledger_entry_id\": 15,\n      \"amount\": \"5000.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Re-allocate A Draft",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-receipts/{{receiptId}}/allocations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-receipts",
                "{{receiptId}}",
                "allocations"
              ]
            },
            "description": "REPLACES the allocations: somebody changing their mind about which invoices a cheque covers, not adding to them. Accumulating would double the settlement.\n\nA posted receipt refuses this.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"allocations\": [\n    {\n      \"customer_ledger_entry_id\": 12,\n      \"amount\": \"15000.0000\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Post A Receipt",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-receipts/{{receiptId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-receipts",
                "{{receiptId}}",
                "post"
              ]
            },
            "description": "The money is in and the balance moves.\n\nThe receipt must EQUAL what it allocates. A cheque for more than the invoices it settles is a payment ON ACCOUNT — a different thing with different accounting, which somebody has to record deliberately rather than have arrive as an arithmetic accident.\n\nFX is realised HERE (B11): an invoice raised at one rate and paid at another produces a real gain or loss, captured per allocation as well as in total, because \"which invoice moved\" is the question an accountant asks next."
          }
        },
        {
          "name": "Cancel A Posted Receipt",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customer-receipts/{{receiptId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customer-receipts",
                "{{receiptId}}",
                "cancel"
              ]
            },
            "description": "A cheque that bounced. Writes the OPPOSITE entry rather than deleting the original: the money WAS recorded as received, and that is a fact about the account rather than a mistake to erase.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Cheque returned unpaid\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        }
      ]
    },
    {
      "name": "Customers - Store Credit (T057)",
      "description": "B8. What the SHOP owes the customer — a SEPARATE ledger from T056's receivables, and never netted against it. Somebody with a 5,000 debt and a 2,000 voucher does not owe 3,000: they owe 5,000 and they hold a 2,000 voucher. Offsetting them is a decision with tax and legal consequences, and not one a balance calculation should make quietly on the shop's behalf.\n\nThey also behave differently. A debt AGES; a voucher EXPIRES. A receipt settles an invoice; a refund to store credit settles nothing. Putting both in one signed column would be arithmetically tidy and wrong in every report anybody actually runs.\n\n**AN ISSUE IS A LAYER.** Same shape as a cost layer, and for the same reason: a customer holding two credits issued on different days has two expiry dates, and a single balance cannot say which lapses next. Spending draws down the one expiring SOONEST — credit about to lapse gets used before credit that would not have. Consuming newest-first would quietly let usable credit expire, which is money the shop keeps by accident and could not defend if a customer asked. Null expiry sorts LAST, exactly as it does in FEFO.\n\n**THE DATE DECIDES, NOT THE STATUS.** `available` sums only layers still in date, so a missed sweep can never let dead credit be spent. But the BALANCE has to fall when credit lapses, and a balance cannot fall without an entry — so `store-credit:expire` writes one, nightly and idempotently. Until it runs, `balance` and `available` differ, and the screen shows both so an overdue sweep is visible rather than silently confusing somebody at a counter.\n\n**EVERY DRAW IS ONE CONDITIONAL UPDATE.** `remaining = remaining - ? WHERE remaining >= ?`, refused when it matches no rows. Two tills spending the same voucher both read 2,000 and both write 0 otherwise — and unlike stock, where the goods physically run out, a balance has no limit on the overspend. Which is also why store credit is refused OFFLINE (OFFLINE_SYNC_DESIGN §6).",
      "item": [
        {
          "name": "Store Credit Position",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/store-credit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "store-credit"
              ]
            },
            "description": "`balance` is what the shop owes; `available` is what can actually be SPENT today. They differ when credit has lapsed but the nightly sweep has not run yet, and both are shown on purpose — a single figure would either overstate what a customer can spend or hide an overdue sweep.\n\n`layers` lists what they hold, soonest to expire, each with `days_left` — which is what a counter says when asked \"when does this run out?\""
          }
        },
        {
          "name": "Store Credit History",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/store-credit/entries?per_page=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "store-credit",
                "entries"
              ],
              "query": [
                {
                  "key": "per_page",
                  "value": "50"
                }
              ]
            },
            "description": "Issues, spends and expiries in one list, because \"where did my credit go\" is answered by all three together."
          }
        },
        {
          "name": "Issue Store Credit",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/store-credit",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "store-credit"
              ]
            },
            "description": "Goodwill, a gift voucher, an opening balance at go-live. A refund that goes to credit issues its own from the RETURN, so it carries the return's reference.\n\n`expires_on` is optional and defaults to `sales.store_credit_expiry_days` — which itself defaults to NEVER. Credit that silently disappears is the shop keeping money a customer believes they have, so a shop that wants an expiry has to say so.\n\n`notes` is REQUIRED: a voucher nobody can explain later is a voucher somebody will query.\n\nNeeds `customers.store_credit.issue`, which is its own permission because issuing credit is giving money away.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"1500.0000\",\n  \"expires_on\": \"2027-02-28\",\n  \"notes\": \"Goodwill after the delivery went missing\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Take Store Credit Back",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/store-credit/adjust",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "store-credit",
                "adjust"
              ]
            },
            "description": "A NEGATIVE amount draws on the layers exactly as a purchase would — off the soonest to expire — so the balance can never exceed what is spendable.\n\nA POSITIVE amount is routed through the issue path instead, so every increase leaves a LAYER behind it. Without that the balance could exceed the layers and nobody could see why.\n\nSeparately permissioned: reducing somebody's credit is the shop taking back something the customer believes they hold.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"-300.0000\",\n  \"reason\": \"Issued to the wrong customer\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Prove The Balance",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/customers/{{customerId}}/store-credit/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "customers",
                "{{customerId}}",
                "store-credit",
                "reconcile"
              ]
            },
            "description": "The projection against a SUM of the entries. Same arrangement as `stock_balances` and the receivables ledger, exposed for the same reason: a figure nobody can check is a figure nobody should trust."
          }
        }
      ]
    },
    {
      "name": "Customer Messaging (T058)",
      "description": "B16. SMS and email templates, campaigns, and a delivery log that answers \"what did we actually send this customer\".\n\n**CONSENT IS CHECKED WHEN A MESSAGE IS SENT, NOT WHEN IT IS QUEUED.** A marketing message can sit in a queue for minutes, and somebody who opts out in that window must not receive it. The refusal is RECORDED as `suppressed` rather than thrown away, because \"why did this customer not get the offer\" is a question with an answer.\n\n`suppressed` is deliberately NOT a failure. The message was correctly not sent; counting it as an error would bury real delivery problems under a pile of working ones.\n\n**A SERVICE MESSAGE IS NOT MARKETING.** \"Your order is ready\" is not an offer, and a shop that stops sending it because somebody unsubscribed from promotions has broken the thing the customer actually wanted. Only `marketing` consults the consent recorded in T055.\n\n**THE LOG FREEZES WHAT WAS SENT** — the rendered subject and body, and the address as it was at the time. A template edited next week must not change what the log says went out last week, and a customer changing their number must not rewrite where a past message went. Same rule as a receipt snapshot.\n\n**A MISSING VARIABLE IS AN ERROR, NOT AN EMPTY STRING.** \"Dear ,\" reaching a customer is worse than a refused send: the refusal is visible to the shop, and the empty name is visible to the customer.\n\n**THE DEFAULT GATEWAY IS `log`, ON PURPOSE.** A system that starts sending real messages the moment it is installed is one that texts somebody's customers during a demo, during a data import, or while a developer tests a reminder job against a copy of production. A shop turns a real gateway on deliberately, one channel at a time, in `config/messaging.php`.\n\nSMS has no standard API — every provider is a slightly different HTTP call and a Sri Lankan shop will be using one of half a dozen local ones — so the endpoint, method and FIELD NAMES are configuration rather than code.",
      "item": [
        {
          "name": "List Templates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/message-templates?channel=sms&purpose=marketing",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "message-templates"
              ],
              "query": [
                {
                  "key": "channel",
                  "value": "sms"
                },
                {
                  "key": "purpose",
                  "value": "marketing"
                }
              ]
            },
            "description": "One row per code, channel AND language. The code is what the system refers to; the language is a variant of it, so `order.ready` in Sinhala and in English are two rows naming one thing."
          }
        },
        {
          "name": "Write A Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/message-templates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "message-templates"
              ]
            },
            "description": "`{{ variable }}` placeholders are filled at send time. `variables` is what the EDITOR shows; `placeholders` in the response is what the text actually uses, read back from the body — the two can disagree, and the text is what a send has to satisfy.\n\n`purpose` decides whether an opt-out silences it. `segments` reports what a thousand of these will cost, because SMS is billed per part and that is a decision somebody should make before the campaign, not after the invoice.\n\nSMS has no subject, and the field is left null rather than putting something on the form that nothing will ever read.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"statement.due\",\n  \"channel\": \"sms\",\n  \"language\": \"en\",\n  \"name\": \"Statement due\",\n  \"purpose\": \"service\",\n  \"body\": \"Hello {{ customer_name }}, {{ amount }} is due on {{ due_date }}.\",\n  \"variables\": [\n    \"customer_name\",\n    \"amount\",\n    \"due_date\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Template",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/message-templates/{{templateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "message-templates",
                "{{templateId}}"
              ]
            },
            "description": "Editing a template does NOT change anything already sent — the log keeps its own rendered copy.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"body\": \"Hi {{ customer_name }}, {{ amount }} falls due {{ due_date }}.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete A Template",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/message-templates/{{templateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "message-templates",
                "{{templateId}}"
              ]
            },
            "description": "Soft delete. Messages already sent are unchanged, for the same reason."
          }
        },
        {
          "name": "Preview",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/messages/preview",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "messages",
                "preview"
              ]
            },
            "description": "What would this actually say? A template whose first reader is a customer is a template nobody checked — the same reasoning as Screen 8.3's Test panel.\n\nRefuses on a missing variable exactly as a send would, so the check is real rather than decorative.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"template_code\": \"statement.due\",\n  \"channel\": \"sms\",\n  \"variables\": {\n    \"customer_name\": \"Nimal\",\n    \"amount\": \"4,500\",\n    \"due_date\": \"15 Sep\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Send To One Customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/messages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "messages"
              ]
            },
            "description": "Queued and attempted immediately.\n\nThe response says plainly when it was NOT sent: a `suppressed` status with the reason, rather than a cheerful \"sent\" for a message that never left. Idempotent — a retried request cannot message somebody twice.\n\nThe recipient comes from the customer's own record unless one is given.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer_id\": 12,\n  \"channel\": \"sms\",\n  \"template_code\": \"order.ready\",\n  \"variables\": {\n    \"customer_name\": \"Nimal\",\n    \"reference\": \"INV-CMB-000123\"\n  },\n  \"reference_type\": \"sale\",\n  \"reference_id\": 123,\n  \"idempotency_key\": \"order-123-ready\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Send A Campaign",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/messages/campaign",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "messages",
                "campaign"
              ]
            },
            "description": "One template to many customers. Each recipient gets their OWN log row and their own consent check, so a hundred recipients produce a hundred answerable outcomes rather than one summary nobody can act on.\n\nThe response says how many were QUEUED, not sent: consent is checked at delivery, so the real answer comes from the log.\n\n`campaign_key` makes the whole run idempotent — running it twice does not message anybody twice. A customer with no address for the channel is SKIPPED, not fatal: one unreachable person must not stop the campaign.\n\nNeeds `messaging.campaign`, its own permission, because sending to one customer and sending to a thousand are different acts with different consequences.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"channel\": \"sms\",\n  \"template_code\": \"offer.december\",\n  \"group_id\": 2,\n  \"campaign_key\": \"december-2026\",\n  \"variables\": {\n    \"offer\": \"10% off everything\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delivery Log",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/messages?customer_id=12&status=failed&per_page=50",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "messages"
              ],
              "query": [
                {
                  "key": "customer_id",
                  "value": "12"
                },
                {
                  "key": "status",
                  "value": "failed"
                },
                {
                  "key": "per_page",
                  "value": "50"
                }
              ]
            },
            "description": "What was sent, to whom, and what happened. Every row carries the FROZEN rendered text and the address as it was at the time.\n\nFilter by `status` to separate the four outcomes: `queued`, `sent`, `failed` (the gateway did not take it) and `suppressed` (the customer had opted out, and it was correctly not sent).\n\nFILTERS (T063): `reference_type` and `reference_id` answer what was sent about ONE document. \"Did we tell this customer their cash-on-delivery parcel was coming?\" is the first question anybody asks when a delivery fails for \"customer could not pay\", and these columns exist precisely to answer it. A confirmation is logged against the `order`, a dispatch against the `order_fulfilment` and an arrival against the `delivery`, so asking about one alone answers part of the question. `template_code` narrows to one kind of message."
          }
        },
        {
          "name": "Retry One Message",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/messages/{{messageId}}/retry",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "messages",
                "{{messageId}}",
                "retry"
              ]
            },
            "description": "By hand, for a failure somebody has looked at. The scheduler retries RETRYABLE failures on its own with growing gaps (`messaging.retry_backoff`) — a provider having a bad minute recovers on its own, and hammering it makes things worse.\n\nA REFUSAL is never retried automatically: a malformed number will still be malformed, and retrying it five times only delays somebody finding out."
          }
        }
      ]
    },
    {
      "name": "Catalogue - Barcodes",
      "item": [
        {
          "name": "List Barcodes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes?product_variant_id={{variantId}}&search=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes"
              ],
              "query": [
                {
                  "key": "product_variant_id",
                  "value": "{{variantId}}"
                },
                {
                  "key": "search",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Lookup Barcode (scan)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes/lookup?barcode=5012345678900",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes",
                "lookup"
              ],
              "query": [
                {
                  "key": "barcode",
                  "value": "5012345678900"
                }
              ]
            },
            "description": "Resolves a scan to its SKU. Deliberately not branch-scoped: a barcode identifies a product, not a location, so goods receipt in one branch must resolve a code first entered in another."
          }
        },
        {
          "name": "Preview Barcode (SVG)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes/preview?value=5012345678900&symbology=ean13&height=60",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes",
                "preview"
              ],
              "query": [
                {
                  "key": "value",
                  "value": "5012345678900"
                },
                {
                  "key": "symbology",
                  "value": "ean13"
                },
                {
                  "key": "height",
                  "value": "60"
                }
              ]
            },
            "description": "Returns image/svg+xml, not JSON."
          }
        },
        {
          "name": "Add Barcode",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": \"{{variantId}}\",\n  \"barcode\": \"5012345678900\",\n  \"symbology\": \"ean13\",\n  \"uom_id\": null,\n  \"is_primary\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Omit `symbology` to have it detected from the code. A code that looks like an EAN or UPC but fails its check digit is rejected as a typo."
          }
        },
        {
          "name": "Generate In-Store Barcodes",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes/generate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes",
                "generate"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_ids\": [\n    \"{{variantId}}\"\n  ],\n  \"prefix\": \"20\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Mints EAN-13s in the GS1 restricted-circulation range for SKUs that have no code yet. SKUs that already carry one are skipped, never overwritten."
          }
        },
        {
          "name": "Update Barcode",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes/{{barcodeId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes",
                "{{barcodeId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"uom_id\": null,\n  \"status\": \"active\",\n  \"is_primary\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "The digits themselves are immutable — they are printed on stock already on a shelf. Delete the code and add the right one instead."
          }
        },
        {
          "name": "Delete Barcode",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/barcodes/{{barcodeId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "barcodes",
                "{{barcodeId}}"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Catalogue - Labels & Print Queue",
      "item": [
        {
          "name": "List Label Templates",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-templates?status=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-templates"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                }
              ]
            }
          }
        },
        {
          "name": "Create Label Template",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-templates",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-templates"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Shelf tag 50x30\",\n  \"width_mm\": 50,\n  \"height_mm\": 30,\n  \"symbology\": \"ean13\",\n  \"show_price\": true,\n  \"show_unit\": true,\n  \"show_tax_note\": false,\n  \"copies_per_sku\": 1,\n  \"printer\": null,\n  \"is_default\": true,\n  \"auto_queue_on_receipt\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "`auto_queue_on_receipt` is the print-from-goods-receipt hook: the template carrying it is what goods receipt prints with. At most one template holds each of `is_default` and `auto_queue_on_receipt`."
          }
        },
        {
          "name": "Update Label Template",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-templates/{{labelTemplateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-templates",
                "{{labelTemplateId}}"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Shelf tag 50x30\",\n  \"width_mm\": 50,\n  \"height_mm\": 30,\n  \"symbology\": \"ean13\",\n  \"show_price\": true,\n  \"show_unit\": true,\n  \"show_tax_note\": false,\n  \"copies_per_sku\": 2,\n  \"printer\": \"Zebra GK420d\",\n  \"is_default\": true,\n  \"auto_queue_on_receipt\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete Label Template",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-templates/{{labelTemplateId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-templates",
                "{{labelTemplateId}}"
              ]
            }
          }
        },
        {
          "name": "List Print Jobs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs?status=&source=&per_page=25",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs"
              ],
              "query": [
                {
                  "key": "status",
                  "value": ""
                },
                {
                  "key": "source",
                  "value": ""
                },
                {
                  "key": "per_page",
                  "value": "25"
                }
              ]
            }
          }
        },
        {
          "name": "Queued Job Count",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs/queued-count",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs",
                "queued-count"
              ]
            },
            "description": "Drives the \"Print Queue (12)\" badge on Screen 3.7."
          }
        },
        {
          "name": "Queue Labels",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs"
              ]
            },
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label_template_id\": \"{{labelTemplateId}}\",\n  \"branch_id\": null,\n  \"notes\": \"Shelf re-tag\",\n  \"lines\": [\n    {\n      \"product_variant_id\": \"{{variantId}}\",\n      \"copies\": 3\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "description": "Captures the name, barcode, price and unit onto each job line at queue time. A later price change does not alter a job already queued."
          }
        },
        {
          "name": "Get Print Job",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs/{{labelJobId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs",
                "{{labelJobId}}"
              ]
            }
          }
        },
        {
          "name": "Print Sheet (HTML)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs/{{labelJobId}}/sheet",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs",
                "{{labelJobId}}",
                "sheet"
              ]
            },
            "description": "Returns text/html sized to the label via an @page rule — the printed page IS the label. Rendered from the captured lines, so a reprint is identical to the original rather than today’s prices."
          }
        },
        {
          "name": "Mark Job Printed",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs/{{labelJobId}}/printed",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs",
                "{{labelJobId}}",
                "printed"
              ]
            }
          }
        },
        {
          "name": "Cancel Job",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/label-jobs/{{labelJobId}}/cancel",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "label-jobs",
                "{{labelJobId}}",
                "cancel"
              ]
            }
          }
        }
      ]
    },
    {
      "name": "Finance - Income & Expenses (T065)",
      "description": "Money in and money out that is neither a sale nor a purchase (Screen 11.1). The CATEGORY decides the direction and it is captured onto the entry, so recategorising next month cannot flip the sign of something already reported on. The amount is always positive; posting is a separate act from typing.",
      "item": [
        {
          "name": "Finance Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "options"
              ]
            },
            "description": "Directions and entry states for the screen's pick lists (T065, Screen 11.1)."
          }
        },
        {
          "name": "List Entries",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries?from=2026-09-01&to=2026-09-30&status=posted",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-09-01"
                },
                {
                  "key": "to",
                  "value": "2026-09-30"
                },
                {
                  "key": "status",
                  "value": "posted"
                }
              ]
            },
            "description": "Money in and money out that is neither a sale nor a purchase. Newest by DATE first, then by id — the date is what a person is looking for, and the id keeps the sort total so two entries on the same day cannot come back differently on two servers."
          }
        },
        {
          "name": "Record An Entry",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries"
              ]
            },
            "description": "Saved as a DRAFT. Nothing counts towards a period figure until somebody posts it — a report that quietly included drafts would move every time anybody opened a form.\\n\\nThe DIRECTION is not sent and cannot be: it is a property of the CATEGORY, because rent is never income and scrap sales are never an expense. It is CAPTURED onto the entry, so recategorising next month cannot silently flip the sign of something already posted.\\n\\nThe amount is always POSITIVE. A signed amount would let somebody record negative income, which is an expense typed in the wrong place and unreadable on every report afterwards.\\n\\nTax is captured at the entry's OWN DATE, so raising the rate next April cannot restate last year's electricity bill. `tax_recoverable` travels with it, because that is what separates VAT a shop can reclaim from VAT it simply pays.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": 1,\n  \"finance_category_id\": 1,\n  \"entry_date\": \"2026-09-01\",\n  \"description\": \"September rent\",\n  \"counterparty\": \"Mr Silva\",\n  \"amount\": \"50000.0000\",\n  \"tax_inclusive\": false,\n  \"payment_method\": \"bank_transfer\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Get An Entry",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/{{financeEntryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "{{financeEntryId}}"
              ]
            },
            "description": "One entry with its category and who posted it."
          }
        },
        {
          "name": "Change A Draft",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/{{financeEntryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "{{financeEntryId}}"
              ]
            },
            "description": "Only while it is a DRAFT. A posted entry has been in a report, and a figure that was reported and then quietly changed is worse than one that was wrong — at least a wrong figure can be found. Correct a posted one by voiding it and raising a new one.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"52000.0000\",\n  \"description\": \"September rent (revised)\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Post An Entry",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/{{financeEntryId}}/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "{{financeEntryId}}",
                "post"
              ]
            },
            "description": "The money actually moved. THIS is the act that puts a figure in a period report, which is why it needs `finance.post` rather than the permission that lets somebody type the entry — recording what the electricity cost and saying it was paid are different acts. Send an Idempotency-Key: a retry must not post it twice."
          }
        },
        {
          "name": "Void An Entry",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/{{financeEntryId}}/void",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "{{financeEntryId}}",
                "void"
              ]
            },
            "description": "It was posted in error. VOIDED, never deleted: the money either moved or it did not, and if it did not, the record of the mistake is itself worth keeping — a deleted row leaves a gap in a numbered sequence somebody will eventually have to explain.\\n\\nA REASON IS REQUIRED. A reversed figure with no reason is indistinguishable next month from one somebody reversed by accident.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Paid twice by mistake\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Delete A Draft",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/{{financeEntryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "{{financeEntryId}}"
              ]
            },
            "description": "A draft nobody posted. Nothing has ever seen it, so there is nothing to unwind. A POSTED entry is refused here — void it instead."
          }
        },
        {
          "name": "Period Summary",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-entries/summary?branch_id=1&from=2026-09-01&to=2026-09-30",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-entries",
                "summary"
              ],
              "query": [
                {
                  "key": "branch_id",
                  "value": "1"
                },
                {
                  "key": "from",
                  "value": "2026-09-01"
                },
                {
                  "key": "to",
                  "value": "2026-09-30"
                }
              ]
            },
            "description": "Income, expense and the net for a branch and a date range, plus what each category came to.\\n\\nPOSTED ENTRIES ONLY. `net` is income less expense and is deliberately NOT called profit: it knows nothing about sales or the cost of goods. `recoverable_tax` is the input tax on these expenses, which is what T069's return takes off the output tax."
          }
        },
        {
          "name": "List Categories",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-categories"
              ]
            },
            "description": "The kinds of money in and out a shop recognises. Each carries its DIRECTION, because that is a property of the thing rather than of each entry."
          }
        },
        {
          "name": "Add A Category",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-categories"
              ]
            },
            "description": "The direction is set once, here. Rent is never income.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"RENT\",\n  \"name\": \"Rent\",\n  \"direction\": \"expense\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Update A Category",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-categories/{{financeCategoryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-categories",
                "{{financeCategoryId}}"
              ]
            },
            "description": "The DIRECTION is deliberately ignored on update, even if sent. Entries captured it, so flipping it would leave a category whose name says one thing and whose history says another.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"RENT\",\n  \"name\": \"Rent and rates\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Retire A Category",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/finance-categories/{{financeCategoryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "finance-categories",
                "{{financeCategoryId}}"
              ]
            },
            "description": "Soft-deleted, so past entries still name what they were filed under."
          }
        }
      ]
    },
    {
      "name": "Finance - Petty Cash (T066)",
      "description": "The cash box a branch keeps for the small things (Screen 11.2). The balance is DERIVED from the movements every time it is asked for, never stored. A spend also files its expense, in the same act. A count is a FACT about what was in the box, not a correction of it — writing a difference off is separate, and separately permissioned.",
      "item": [
        {
          "name": "Petty Cash Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/petty-cash/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "options"
              ]
            },
            "description": "The movement types, so a screen never hard-codes an enum the API owns.\n\nThe AMOUNT on every movement is positive and the TYPE says which way it goes — except an adjustment, which signs itself, because it exists precisely to correct a difference that could go either way."
          }
        },
        {
          "name": "List Cash Boxes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/petty-cash?active=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash"
              ],
              "query": [
                {
                  "key": "active",
                  "value": "1"
                }
              ]
            },
            "description": "Every box, each with its balance.\n\nTHERE IS NO BALANCE COLUMN. It is the sum of the movements, computed when you ask for it, exactly as a till drawer's expected cash is. A stored running total incremented per spend is the same lost update as `stock_balances` — two people recording a payment at once both read 5,000 and both write 4,500 — except what it corrupts is money in a box somebody is holding, and the first anybody knows is a count that will not reconcile.\n\n`float_amount` is what the box holds when FULL: a target a top-up restores it to, never a balance."
          }
        },
        {
          "name": "Open A Cash Box",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"PC-CMB\",\n  \"name\": \"Colombo counter float\",\n  \"branch_id\": 1,\n  \"custodian_id\": 2,\n  \"float_amount\": \"20000.0000\",\n  \"notes\": \"Held in the manager's drawer.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash"
              ]
            },
            "description": "A box sits at a BRANCH and has somebody's name on it — that is what makes a count somebody's responsibility rather than nobody's.\n\nThe branch is asked for, never inherited from the topbar: head office is exactly the people with no branch pinned. 422 if none is given."
          }
        },
        {
          "name": "Get A Cash Box",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}"
              ]
            },
            "description": "The box, its derived balance, and the last five counts — each keeping BOTH what was counted and what the book expected, with the difference between them."
          }
        },
        {
          "name": "Update A Cash Box",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"PC-CMB\",\n  \"name\": \"Colombo counter float\",\n  \"float_amount\": \"25000.0000\",\n  \"status\": \"active\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}"
              ]
            },
            "description": "The BRANCH is fixed and silently ignored here. Movements are recorded against it, and moving the book would leave a branch's cash history pointing somewhere else.\n\nClosing a book stops it taking new movements."
          }
        },
        {
          "name": "Statement",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}/statement?from=2026-09-01&to=2026-09-30",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}",
                "statement"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-09-01"
                },
                {
                  "key": "to",
                  "value": "2026-09-30"
                }
              ]
            },
            "description": "The book with a RUNNING BALANCE down the page, opened at the balance before the window — otherwise the figures on screen are a total that starts at zero halfway through the book's life.\n\nThe running figure is computed as the rows are walked, never stored: the balance after movement 40 is the sum of the first 40, and anything else is a number that can drift from the rows above it.\n\n`shortfall` is what a top-up would have to be to restore the float."
          }
        },
        {
          "name": "Top Up",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"20000.0000\",\n  \"movement_date\": \"2026-09-01\",\n  \"description\": \"Restored the float\",\n  \"counterparty\": \"Head office\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}/top-up",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}",
                "top-up"
              ]
            },
            "description": "Put money in the box. The amount is POSITIVE — a negative top-up is a spend typed in the wrong place, and unreadable on every report afterwards.\n\nCarries an `Idempotency-Key`: a retried top-up would put the money in twice, and unlike a duplicated sale nobody can look at the cash afterwards and tell which of the two entries was real."
          }
        },
        {
          "name": "Spend From The Box",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"finance_category_id\": 1,\n  \"amount\": \"850.0000\",\n  \"movement_date\": \"2026-09-05\",\n  \"description\": \"Tea for the counter\",\n  \"counterparty\": \"Sunil Stores\",\n  \"tax_inclusive\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}/spend",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}",
                "spend"
              ]
            },
            "description": "Something was bought out of the box.\n\nThis files the EXPENSE too (T065) and points the movement at it: \"paid 500 for tea out of petty cash\" is two facts about one act, and entering them separately is how the expense report and the cash box come to disagree. The entry is posted at once, because the money has demonstrably gone.\n\nThe category must be an EXPENSE one — filing it against income would add to the shop's takings for buying tea. On a tax-EXCLUSIVE entry the GROSS leaves the box, because that is what the hand holding the notes paid.\n\nRefused if the box does not hold it: unlike stock — where the goods physically left and refusing would record a fiction — nobody can hand over cash that is not there."
          }
        },
        {
          "name": "Return Unspent Cash",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"150.0000\",\n  \"description\": \"Change from the courier float\",\n  \"counterparty\": \"Nimal\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}/return",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}",
                "return"
              ]
            },
            "description": "Money handed back unspent. NOT an expense — nothing was bought, and counting it as one would put a category total above what the shop actually spent."
          }
        },
        {
          "name": "Count The Box",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"counted_amount\": \"19850.0000\",\n  \"counted_on\": \"2026-09-30\",\n  \"variance_reason\": \"Signed off by the branch manager\",\n  \"write_off\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}/count",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}",
                "count"
              ]
            },
            "description": "Somebody opened the box and counted it.\n\nBOTH figures are kept — what was counted and what the movements say should have been there — with the variance DERIVED from them. Storing only the agreed figure throws away the evidence at exactly the point somebody needs it, and a form asking for all three invites them not to add up.\n\nA difference must be EXPLAINED: one nobody wrote a reason for is indistinguishable next month from one nobody noticed.\n\nA count MOVES NOTHING. `write_off: true` raises the adjustment that brings the book into line, and needs `finance.petty_cash.adjust` — counting the box and deciding a shortfall is simply gone are different acts.\n\nMeasured against the balance on `counted_on`, not against today."
          }
        },
        {
          "name": "Adjust The Book",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"-150.0000\",\n  \"reason\": \"Receipt lost; counted short at month end\",\n  \"movement_date\": \"2026-09-30\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/petty-cash/{{pettyCashBookId}}/adjust",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "petty-cash",
                "{{pettyCashBookId}}",
                "adjust"
              ]
            },
            "description": "Bring the book into line with what is actually there.\n\nThe amount is SIGNED here — negative if the box is short, positive if there is more in it than the book says. It is the only movement type that can go either way, and the only one that has to justify itself: \"the number was wrong\" with no reason is indistinguishable from somebody making the number say what they wanted.\n\nSeparately permissioned from counting."
          }
        }
      ]
    },
    {
      "name": "Finance - Assets (T067)",
      "description": "The fixed asset register (Screen 11.3). Depreciation is a LEDGER, not a formula run at read time: each period is a row that captures the method, rate and life it was worked out from, so changing them next March affects what happens next and nothing that already happened. A charge files its expense; a disposal keeps the asset with the date it left and reports the gain or the loss; and `reconcile` proves the register against its own schedules.",
      "item": [
        {
          "name": "Asset Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "options"
              ]
            },
            "description": "The methods, the states, the event types and who a box can be handed to, so a screen never hard-codes an enum the API owns.\n\n`first_period` says how the month of PURCHASE is charged: `pro_rata` for the days it was actually held, `full_month` for the convention. Both are defensible and shops use both; the choice is captured onto every charge through `period_fraction`, so changing it cannot restate anything already posted."
          }
        },
        {
          "name": "List The Register",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets?status=active&held=1&search=chiller",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "active"
                },
                {
                  "key": "held",
                  "value": "1"
                },
                {
                  "key": "search",
                  "value": "chiller"
                }
              ]
            },
            "description": "What the shop owns.\n\n`carrying_amount` and `accumulated_depreciation` are a PROJECTION of each asset's charges - kept on the row because a register of thousands cannot sum a schedule per row per page. `GET /assets/reconcile` proves them against the ledger.\n\n`held=1` narrows it to what the shop still has. Disposed assets STAY on the register: one that forgets what was sold cannot say what the business owned in March, which is most of what a register is for."
          }
        },
        {
          "name": "Add An Asset",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"asset_category_id\": 1,\n  \"branch_id\": 1,\n  \"name\": \"Display chiller (front)\",\n  \"code\": \"CHL-04\",\n  \"acquired_on\": \"2026-01-15\",\n  \"depreciation_start\": \"2026-02-01\",\n  \"acquisition_cost\": \"600000.0000\",\n  \"residual_value\": \"60000.0000\",\n  \"depreciation_method\": \"straight_line\",\n  \"useful_life_months\": 60,\n  \"serial_number\": \"SN-9931-A\",\n  \"custodian_id\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets"
              ]
            },
            "description": "The method, life and rate are CAPTURED from the class and then belong to the asset. Two chillers bought three years apart may legitimately be written off over different periods, and changing the class next year must not restate everything bought under the old one.\n\n`depreciation_start` is not always the purchase date - a chiller delivered in March and commissioned in May was not earning for two months, and charging those puts a cost against a period that had no benefit from it.\n\n`residual_value` is never written off: a van worth 500,000 as scrap has not lost its entire value. More than the cost is refused.\n\nThe BRANCH is asked for, never inherited: head office has none pinned."
          }
        },
        {
          "name": "Get An Asset",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}"
              ]
            },
            "description": "The asset, its posted SCHEDULE and its EVENTS.\n\nEvery schedule row carries the method, rate and life the charge was worked out from. Extending the asset's life next March changes what happens next and nothing in that column - which is the whole reason depreciation is a ledger here rather than a formula run at read time.\n\nA revaluation event keeps BOTH carrying amounts. Storing only the new one throws away the evidence at exactly the point somebody asks why the register moved."
          }
        },
        {
          "name": "Update An Asset",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Display chiller (rear)\",\n  \"useful_life_months\": 84\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}"
              ]
            },
            "description": "Once a charge has been posted the COST and the START DATE are fixed and silently ignored: they are what every charge so far was worked out from, and moving them would make the schedule disagree with its own rows. The life and the rate can still change, because those legitimately do - and they only affect what happens next."
          }
        },
        {
          "name": "Put An Asset Into Use",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/activate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "activate"
              ]
            },
            "description": "A separate act from entering it, for the same reason posting a finance entry is separate from typing one: a draft is somebody mid-form, and nothing half-entered should start charging a branch every month.\n\nRefused if the method has no figure to work from - straight line with no life would divide by zero and reducing balance with no rate would charge nothing forever. Both look exactly like an asset nobody meant to depreciate, which is the one failure a register cannot afford to make quietly."
          }
        },
        {
          "name": "Delete A Draft",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}"
              ]
            },
            "description": "Only a DRAFT. Something that has been in use is disposed of or written off, and it stays on the register with the date it left."
          }
        },
        {
          "name": "Projected Schedule",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/schedule?periods=24",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "schedule"
              ],
              "query": [
                {
                  "key": "periods",
                  "value": "24"
                }
              ]
            },
            "description": "What it WILL cost, month by month, until it reaches its residual value.\n\nComputed on a COPY of the asset and written nowhere: looking at the future must not change the present. Bounded because reducing balance approaches its residual without ever quite arriving, and a schedule of four thousand rows helps nobody."
          }
        },
        {
          "name": "Charge What Is Due",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"up_to\": \"2026-06-30\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/depreciate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "depreciate"
              ]
            },
            "description": "Posts one row per period from the last one charged up to `up_to`.\n\nSAFE TO SEND TWICE. One charge per asset per period, enforced by a unique index rather than by the caller being careful: a scheduled job gets run twice - by a retry, by somebody catching up a missed month, by two servers without a lock - and charging a period twice halves an asset's life without anybody choosing to.\n\nEach charge files its EXPENSE (T065) in the class's expense account, because depreciation is a real cost and a month's figures without it are short by the one thing nobody wrote a cheque for.\n\nThe last period charges whatever is left down to the residual, never a full period's worth. Without that, straight line leaves a rounding remainder carrying -0.0003 and reducing balance runs forever in ever-smaller amounts.\n\nCatching up is bounded by `assets.catch_up_months`: an asset entered with a start date three years ago should not silently post 36 periods."
          }
        },
        {
          "name": "Reverse The Last Charge",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Charged against the wrong branch\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/reverse",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "reverse"
              ]
            },
            "description": "Refuses anything but the LAST period: reversing a middle one would leave every row after it worked out from a figure that no longer holds.\n\nThe expense is VOIDED, never deleted - it was reported, and a deleted row leaves a gap in a numbered sequence somebody has to explain. A reason is required."
          }
        },
        {
          "name": "Move It To Another Branch",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"to_branch_id\": 2,\n  \"event_date\": \"2026-06-01\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/transfer",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "transfer"
              ]
            },
            "description": "A FACT with a date, not an edit to a column. \"Where was the chiller in March\" is a question a branch P&L depends on, and an overwritten column cannot answer it.\n\nThe event names both ends, exactly like a stock transfer."
          }
        },
        {
          "name": "Revalue It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"carrying_amount\": \"700000.0000\",\n  \"reason\": \"Independent valuation after the refit\",\n  \"event_date\": \"2026-07-01\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/revalue",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "revalue"
              ]
            },
            "description": "Changes the basis GOING FORWARD. Prior charges stand, because they were right on the information available when they were made - the same reason a price change supersedes rather than edits.\n\nUP is a revaluation and DOWN an impairment, and the response says which: opposite judgements with opposite consequences, and a report showing one figure could not tell a shop which it was.\n\nA reason is required - a register that moved for no stated reason is indistinguishable from a typo. Below the residual value is refused: either the residual is wrong or this is a write-off, and both are decisions somebody has to make deliberately."
          }
        },
        {
          "name": "Dispose Of It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"proceeds\": \"650000.0000\",\n  \"disposed_on\": \"2026-08-10\",\n  \"reason\": \"Sold to a dealer\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/assets/{{assetId}}/dispose",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "{{assetId}}",
                "dispose"
              ]
            },
            "description": "Sold, or scrapped. IRREVERSIBLE, which is why it carries an idempotency key.\n\nThe asset STAYS on the register with the date it left and what it fetched. The difference between the proceeds and the carrying amount is a gain or a loss, and BOTH are filed as such (T065) - a register that netted them could not say whether a shop's disposals made money.\n\n`write_off: true` is for what happens when there is nothing to show for it - stolen, destroyed, scrapped - and refuses proceeds, because something that fetched money is a disposal. It has to say what happened to it."
          }
        },
        {
          "name": "Prove The Register",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/assets/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "assets",
                "reconcile"
              ]
            },
            "description": "Does every carrying amount agree with the charges behind it?\n\nThe same job `inventory:reconcile` does for stock, and for the same reason: a projection nobody proves is a number that quietly stops being true. A difference is a bug to trace, never a number to adjust."
          }
        },
        {
          "name": "List Asset Classes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/asset-categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "asset-categories"
              ]
            },
            "description": "What a new asset in each class DEFAULTS to - not what it is bound by. A class with no expense account is flagged on screen, because its charges reach no report."
          }
        },
        {
          "name": "Add An Asset Class",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"REFRIG\",\n  \"name\": \"Refrigeration\",\n  \"depreciation_method\": \"straight_line\",\n  \"useful_life_months\": 60,\n  \"expense_category_id\": 3,\n  \"gain_category_id\": 4,\n  \"loss_category_id\": 5\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/asset-categories",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "asset-categories"
              ]
            },
            "description": "Where the periodic charge is filed, and where a profit or a loss on sale goes. The gain and the loss accounts are SEPARATE on purpose: they are opposite facts, and one figure could not tell a shop which it was looking at."
          }
        },
        {
          "name": "Update An Asset Class",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"REFRIG\",\n  \"name\": \"Refrigeration\",\n  \"useful_life_months\": 84,\n  \"depreciation_method\": \"straight_line\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/asset-categories/{{assetCategoryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "asset-categories",
                "{{assetCategoryId}}"
              ]
            },
            "description": "Changing a class affects assets added AFTER it. Everything already on the register captured its own method and life at creation."
          }
        },
        {
          "name": "Retire An Asset Class",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/asset-categories/{{assetCategoryId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "asset-categories",
                "{{assetCategoryId}}"
              ]
            },
            "description": "Refused while assets are filed under it - deleting it would leave them describing nothing."
          }
        }
      ]
    },
    {
      "name": "Finance - Liabilities (T068)",
      "description": "What the shop owes over time (Screen 11.4). ONLY the interest part of a repayment is an expense; the principal is the debt going down, and booking the whole instalment as a cost is wrong in two directions at once. The schedule is a ledger that captures the rate and balance every instalment was worked out from, and flat and reducing are different loans even when the quoted number is the same.",
      "item": [
        {
          "name": "Liability Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "options"
              ]
            },
            "description": "The kinds, the states, and the two methods that decide what a debt costs.\n\nEach INTEREST method carries its own `caution`. \"12% flat\" and \"12% reducing\" are quoted interchangeably and are not interchangeable: flat charges 12% of the ORIGINAL principal every year for the whole term, however much has been repaid, which is roughly 22% in reducing-balance terms. The warning travels with the option so a screen and the server cannot disagree about what a shop is signing."
          }
        },
        {
          "name": "Quote It Before Committing",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"principal\": \"1000000.0000\",\n  \"term_months\": 12,\n  \"annual_rate\": \"12\",\n  \"interest_method\": \"reducing_balance\",\n  \"repayment_method\": \"equal_instalment\",\n  \"first_due_on\": \"2026-10-01\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/liabilities/quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "quote"
              ]
            },
            "description": "What this WOULD cost. Written nowhere.\n\n\"12% over five years\" means nothing to most people; \"you will pay back 1,340,000 on a million, and 340,000 of it is interest\" means a great deal.\n\nTry the same body with `interest_method` flipped between `reducing_balance` and `flat`: the same quoted rate nearly doubles the interest."
          }
        },
        {
          "name": "Quote An Existing Liability",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/{{liabilityId}}/quote",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "{{liabilityId}}",
                "quote"
              ]
            },
            "description": "The same arithmetic, for a liability already on the register - what its own terms would cost over the whole term. Useful on a DRAFT, before it is drawn down."
          }
        },
        {
          "name": "List Liabilities",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities?status=active&search=van",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "active"
                },
                {
                  "key": "search",
                  "value": "van"
                }
              ]
            },
            "description": "What the shop owes over time — a bank loan, a lease, the hire purchase on the van. NOT the same thing as a supplier invoice: trade credit is short, unstructured and carries no interest, and it is modelled separately (T032).\n\n`outstanding_principal` is a PROJECTION of the payments, proved by `GET /liabilities/reconcile`."
          }
        },
        {
          "name": "Add A Liability",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"branch_id\": 1,\n  \"name\": \"Van loan\",\n  \"type\": \"loan\",\n  \"lender\": \"Commercial Bank\",\n  \"account_reference\": \"TL-4471\",\n  \"principal_amount\": \"1000000.0000\",\n  \"annual_rate\": \"12\",\n  \"interest_method\": \"reducing_balance\",\n  \"repayment_method\": \"equal_instalment\",\n  \"term_months\": 12,\n  \"started_on\": \"2026-09-01\",\n  \"first_due_on\": \"2026-10-01\",\n  \"interest_category_id\": 3\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/liabilities",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities"
              ]
            },
            "description": "Entered as a DRAFT: nothing is owed and no schedule exists until it is drawn down. A draft is somebody mid-form, and nothing half-entered should appear on a balance sheet or start counting instalments as overdue.\n\n`interest_category_id` is where the INTEREST is filed (T065). There is deliberately no account for the principal — it is not an expense, and offering somewhere to put it invites somebody to.\n\nA rate of zero on an interest-bearing method is REFUSED: \"interest free\" is a thing somebody meant to say, and a report has to be able to tell it from a field nobody filled in."
          }
        },
        {
          "name": "Get A Liability",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/{{liabilityId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "{{liabilityId}}"
              ]
            },
            "description": "The liability, its SCHEDULE and every payment.\n\nEach instalment carries its split: `interest_amount` is the cost and `principal_amount` is the debt going down. Two numbers that go to different places, worked out from a balance and a rate captured beside them — so refinancing next year changes what happens next and nothing already paid."
          }
        },
        {
          "name": "Update A Liability",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Van loan (Isuzu)\",\n  \"lender\": \"Commercial Bank\",\n  \"interest_category_id\": 3\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/liabilities/{{liabilityId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "{{liabilityId}}"
              ]
            },
            "description": "Once a schedule exists the TERMS are fixed and silently ignored: every instalment was worked out from them, and changing them behind the schedule would make it disagree with itself. The name, the lender and the interest account can still change."
          }
        },
        {
          "name": "Draw It Down",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/{{liabilityId}}/activate",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "{{liabilityId}}",
                "activate"
              ]
            },
            "description": "Writes the schedule and puts the debt on the books.\n\nA separate act from entering it, and a separate permission — the same reason posting a finance entry is separate from typing one."
          }
        },
        {
          "name": "Delete A Draft",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/{{liabilityId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "{{liabilityId}}"
              ]
            },
            "description": "Only a DRAFT. Something drawn down is settled, written off or cancelled, and it keeps its schedule and every payment."
          }
        },
        {
          "name": "Record A Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"88848.7888\",\n  \"paid_on\": \"2026-10-01\",\n  \"method\": \"standing order\",\n  \"payment_reference\": \"SO-99120\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/liabilities/{{liabilityId}}/pay",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "{{liabilityId}}",
                "pay"
              ]
            },
            "description": "Money went to the lender.\n\nONLY THE INTEREST PART IS AN EXPENSE. A shop paying 88,848 has not spent 88,848: 10,000 of it is interest, which is a cost, and 78,848 is principal, which is the debt going down. Booking the whole instalment overstates the month by the principal AND leaves the balance sheet claiming a debt already paid — two errors pointing in opposite directions, neither visible on a bank statement. The response reports both figures.\n\nApplied to the OLDEST instalment that is due, and to the INTEREST before the principal inside it — which is what a lender does, and what makes a part payment reduce the debt by less than it looks.\n\nAnything above what is DUE is a prepayment against principal, not an advance on future instalments: taking those at their scheduled interest would overcharge, because that interest assumed the earlier principal stayed outstanding. On a REDUCING balance the unpaid rows are then rebuilt from the new balance. On a FLAT rate they are not, because flat interest is fixed at drawdown and paying early genuinely does not reduce it."
          }
        },
        {
          "name": "Reverse A Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"The standing order was returned\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/liability-payments/{{liabilityPaymentId}}/reverse",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liability-payments",
                "{{liabilityPaymentId}}",
                "reverse"
              ]
            },
            "description": "The cheque bounced, or it was entered wrongly.\n\nREVERSED, never edited: a payment is a record of money leaving a bank account. The instalments it settled are owed again and its interest expense is VOIDED rather than deleted, because it was reported. A reason is required."
          }
        },
        {
          "name": "Waive An Instalment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"First month waived as part of the facility\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/liability-instalments/{{liabilityInstalmentId}}/waive",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liability-instalments",
                "{{liabilityInstalmentId}}",
                "waive"
              ]
            },
            "description": "The lender let it go.\n\nNOT a payment: the debt reduced without money moving, and a shop that recorded it as one would have cash it cannot account for. The principal still comes off; the interest is never charged rather than paid, so nothing reaches the profit figures."
          }
        },
        {
          "name": "What Is Falling Due",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/due?from=2026-10-01&to=2026-10-31",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "due"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-10-01"
                },
                {
                  "key": "to",
                  "value": "2026-10-31"
                }
              ]
            },
            "description": "Everything due across every liability — the question a shop actually opens this screen to ask, and one that spans liabilities rather than sitting inside one.\n\nThe answer carries `interest_portion` beside `total_due`, because of the money going out this month only that part is a COST. A shop reading the whole figure as an expense would think it was losing money it is not."
          }
        },
        {
          "name": "Prove The Balances",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/liabilities/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "liabilities",
                "reconcile"
              ]
            },
            "description": "Does every outstanding balance agree with the payments behind it?\n\nThe same job `inventory:reconcile` does for stock and `assets/reconcile` for the register. A projection nobody proves is a number that quietly stops being true, and a difference is a bug to trace rather than a number to adjust."
          }
        }
      ]
    },
    {
      "name": "Tax - Periods & Returns (T069)",
      "description": "The return (B10): output tax less input tax, read from what each document RECORDED at the time rather than from today's rates. A PREPARED period is frozen, so a document arriving late is a visible difference rather than a silent rewrite of a declaration. Only RECOVERABLE input tax is reclaimed; the rest is a cost and is reported as one.",
      "item": [
        {
          "name": "Tax Return Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/returns/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "returns",
                "options"
              ]
            },
            "description": "The period states, and where a figure can come from.\n\nEach SOURCE carries the side of the return it falls on. A sales return reduces OUTPUT tax; it does not add to input. The two are not the same, and a shop whose credit notes landed on the input side would be reclaiming tax it never paid."
          }
        },
        {
          "name": "Look At A Period",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/returns/summary?from=2026-09-01&to=2026-09-30",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "returns",
                "summary"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-09-01"
                },
                {
                  "key": "to",
                  "value": "2026-09-30"
                }
              ]
            },
            "description": "What a stretch of time contains, without a period existing for it. Written nowhere.\n\nThis is how a shop looks at next month before deciding to open a period. Every figure is read from CAPTURED columns on posted documents — the percentage, the taxable amount, the tax, and whether it was recoverable — never from a join to `tax_rates`. Rates are effective-dated and change; a return for last quarter must declare what last quarter actually charged."
          }
        },
        {
          "name": "The Audit Trail",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/returns/audit?from=2026-09-01&to=2026-09-30&source=purchase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "returns",
                "audit"
              ],
              "query": [
                {
                  "key": "from",
                  "value": "2026-09-01"
                },
                {
                  "key": "to",
                  "value": "2026-09-30"
                },
                {
                  "key": "source",
                  "value": "purchase"
                }
              ]
            },
            "description": "Every document line behind a figure.\n\n\"Where did the 15% output figure come from\" has to have an answer, and it has to be a list somebody can go and look at. Filter by `source` to narrow it.\n\n`truncated` says plainly when the list has been cut short: one that stops without saying so reads as \"that is all of it\"."
          }
        },
        {
          "name": "List Periods",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/periods?status=filed",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "filed"
                }
              ]
            },
            "description": "The periods, newest first, with what each declared.\n\nNOT branch-scoped. A return is filed by the ORGANISATION: the authority wants one figure for the registration, and a per-branch return would double-count or omit depending on who was looking. Branch appears in the AUDIT, where the question is which shop a figure came from."
          }
        },
        {
          "name": "Open A Period",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"starts_on\": \"2026-09-01\",\n  \"ends_on\": \"2026-09-30\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tax/periods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods"
              ]
            },
            "description": "Periods must NOT overlap: two covering the same day would declare the same document twice, and nothing downstream could tell. MySQL has no exclusion constraint, so it is checked in one place with a message naming the period in the way.\n\nNamed from the dates if you leave `name` out."
          }
        },
        {
          "name": "Get A Period",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/periods/{{taxPeriodId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods",
                "{{taxPeriodId}}"
              ]
            },
            "description": "The period, its frozen breakdown rate by rate, AND how the same period reads now.\n\n`variance` is the reason the figures are frozen at all. It is NULL while the period is open: there is nothing frozen to compare against, and a difference of zero would be a lie about a comparison nobody made."
          }
        },
        {
          "name": "Prepare It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/tax/periods/{{taxPeriodId}}/prepare",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods",
                "{{taxPeriodId}}",
                "prepare"
              ]
            },
            "description": "FREEZES the figures.\n\nThe totals could be computed on every read. They would be wrong the first time anybody backdated an invoice into a period already filed. So preparing copies the figures onto the period, rate by rate, and anything arriving afterwards shows as a DIFFERENCE rather than being absorbed into a figure somebody has signed. That difference is what the next period's adjustment is made from.\n\nRe-preparing a period that is prepared but not FILED is allowed and replaces the frozen lines: that is somebody catching a document they had missed, which is exactly what preparing is for."
          }
        },
        {
          "name": "Reopen It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"September is not closed yet\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tax/periods/{{taxPeriodId}}/reopen",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods",
                "{{taxPeriodId}}",
                "reopen"
              ]
            },
            "description": "Discards the frozen figures and lets the period collect again.\n\nOnly while it is PREPARED. Once filed it is refused: a system that let a submitted declaration be reopened would let the shop's records disagree with what went to the authority, with nothing recording that they do. A mistake found after filing is corrected in the NEXT period."
          }
        },
        {
          "name": "File It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"filing_reference\": \"VAT-2026-09\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tax/periods/{{taxPeriodId}}/file",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods",
                "{{taxPeriodId}}",
                "file"
              ]
            },
            "description": "Records that the declaration went to the authority, with their reference.\n\nSeparate from PREPARING because days pass between them, and separately permissioned because this one cannot be undone."
          }
        },
        {
          "name": "Record It Settled",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"paid_on\": \"2026-10-15\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/tax/periods/{{taxPeriodId}}/pay",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "tax",
                "periods",
                "{{taxPeriodId}}",
                "pay"
              ]
            },
            "description": "The money moved. Defaults to the declared net when no amount is given, and can point at the finance entry (T065) that paid it."
          }
        }
      ]
    },
    {
      "name": "Finance - Reconciliation (T070)",
      "description": "Cash, card, gateway, bank and COD (Screen 11.5, B13). Every stream has TWO figures - what the shop's records say and what the counterparty says - and the useful output is never \"they agree\". It is the handful of items that do not, which is why the exception queue is a first-class endpoint. Both figures are kept and the variance is derived; a match is a fact rather than a computation re-run; and carrying a line forward is not writing it off.",
      "item": [
        {
          "name": "Reconciliation Options",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "options"
              ]
            },
            "description": "The streams, the states, and what each line can become.\n\nEvery SOURCE carries what its other side is called — so a screen says \"acquirer settlement\" rather than \"statement\" — and whether it belongs to a branch. A bank statement belongs to the ORGANISATION, and a form that demanded a branch would make head office pick one arbitrarily."
          }
        },
        {
          "name": "The Exception Queue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations/exceptions?source=card&side=system",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "exceptions"
              ],
              "query": [
                {
                  "key": "source",
                  "value": "card"
                },
                {
                  "key": "side",
                  "value": "system"
                }
              ]
            },
            "description": "Everything still waiting for somebody, across every OPEN reconciliation.\n\nThe screen a shop actually works from. Reconciling is not \"the totals agree\" — it is \"these four items do not, and here they are\". The agreeing 996 lines need nobody's attention, and a workspace that made somebody open each period to find the four would be a report with extra steps.\n\nOldest first, each carrying `age_days`: an exception that has been sitting for three weeks is the one that matters, and a date alone makes somebody work that out."
          }
        },
        {
          "name": "List Reconciliations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations?source=card&status=open",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations"
              ],
              "query": [
                {
                  "key": "source",
                  "value": "card"
                },
                {
                  "key": "status",
                  "value": "open"
                }
              ]
            },
            "description": "Both figures on every row: what the shop's records say, what the counterparty says, and the difference DERIVED from them."
          }
        },
        {
          "name": "Start One",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"source\": \"card\",\n  \"branch_id\": 1,\n  \"counterparty\": \"Commercial Bank acquiring\",\n  \"account_reference\": \"MID-88213\",\n  \"starts_on\": \"2026-09-01\",\n  \"ends_on\": \"2026-09-30\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reconciliations",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations"
              ]
            },
            "description": "Opens a reconciliation and pulls the SHOP'S side into it.\n\nWhere that side comes from depends on the stream: till sessions for cash (the EXPECTED figure — the person who counted is the counterparty here), captured payments for card, gateway and bank, and collected-but-unsettled deliveries for COD (money that is neither a receivable nor cash).\n\nEverything pulled in is an exception until a statement arrives to pair it with."
          }
        },
        {
          "name": "Get One",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}"
              ]
            },
            "description": "The reconciliation, both totals, and every LINE on both sides.\n\nA response carrying only the totals would leave the queue on screen describing the state before the last match."
          }
        },
        {
          "name": "Add The Other Side",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"amount\": \"12000.0000\",\n      \"occurred_on\": \"2026-09-07\",\n      \"reference\": \"STL-1\"\n    },\n    {\n      \"amount\": \"8000.0000\",\n      \"occurred_on\": \"2026-09-08\",\n      \"reference\": \"STL-2\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/lines",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "lines"
              ]
            },
            "description": "The counterparty's statement.\n\nAdding it matches NOTHING: a statement arriving is not the same act as agreeing what it pairs with, and a system that paired on import would decide for somebody."
          }
        },
        {
          "name": "Match What Pairs",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/auto-match",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "auto-match"
              ]
            },
            "description": "Pairs on AMOUNT within a five-day window, one line to one line.\n\nDeliberately conservative: two identical amounts in the window are left alone rather than guessed between, because a wrong pairing is worse than an unpaired item — one is a question, the other is a wrong answer nobody will look at again. The window exists because card and gateway settlements lag by days; matching same-day only would make every month end look like a disaster.\n\nAnything it pairs is marked `auto_matched`, so a reviewer can see what was decided for them."
          }
        },
        {
          "name": "Pair Two Lines",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"line_id\": 1,\n  \"match_line_id\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/match",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "match"
              ]
            },
            "description": "Somebody decided these two are the same movement.\n\nRefused for two lines on the SAME side: a match means two views of one movement, and two records from one side are two movements."
          }
        },
        {
          "name": "Unpair",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/lines/{{reconciliationLineId}}/unmatch",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "lines",
                "{{reconciliationLineId}}",
                "unmatch"
              ]
            },
            "description": "It was paired wrongly. Both ends go back into the queue."
          }
        },
        {
          "name": "Write Off, Or Carry Forward",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"carried_forward\",\n  \"reason\": \"Taken on the 31st, settles on the 1st\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/lines/{{reconciliationLineId}}/resolve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "lines",
                "{{reconciliationLineId}}",
                "resolve"
              ]
            },
            "description": "TWO DIFFERENT ACTS behind one shape, and the response says which.\n\n`written_off` says the amount will never pair — a bank charge nobody recorded, a rounding difference. `carried_forward` says it belongs to the NEXT period — a card taken on the 31st that settles on the 1st is timing, not a loss. Collapsing the two is exactly how real money gets written off at a month end.\n\nA reason is required either way: an amount that vanished with no explanation is indistinguishable next month from one nobody noticed."
          }
        },
        {
          "name": "Agree The Figures",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"variance_reason\": \"Acquirer deducts its fee before settling\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/reconcile",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "reconcile"
              ]
            },
            "description": "Refused while ANYTHING is still unmatched: the exceptions ARE the reconciliation, and agreeing figures while four items are outstanding is agreeing to nothing.\n\nRefused too on an unexplained difference. Agreeing one does not erase it — both figures and the variance survive, with the reason beside them."
          }
        },
        {
          "name": "Close It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "close"
              ]
            },
            "description": "Finished with. Nothing further can be matched or resolved.\n\nOnly a RECONCILED period can be closed: closing an unreconciled one files a difference nobody has looked at."
          }
        },
        {
          "name": "Reopen It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"A line was matched wrongly\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reconciliations/{{reconciliationId}}/reopen",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reconciliations",
                "{{reconciliationId}}",
                "reopen"
              ]
            },
            "description": "Somebody found something. Only while it is reconciled, and it has to say why."
          }
        }
      ]
    },
    {
      "name": "Reports (T071)",
      "description": "The report framework (M13). ONE route runs every report - the key is resolved through an ALLOWLIST in config/reports.php, never out of the container by name, because a key arriving from a request that could name any class is a form field with code execution behind it. reports.view reaches the catalogue; each report carries its OWN permission on its entry. The filters are captured onto every export and printed on the file, a run that was cut short says so, and a schedule stores a CADENCE rather than dates so it cannot go on sending one month's figures forever.",
      "item": [
        {
          "name": "The Catalogue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports"
              ]
            },
            "description": "What this user may run, with the formats, the cadences and the row cap.\n\nFILTERED BY WHAT THEY MAY ACTUALLY RUN. One `reports.view` for everything would mean anybody who can read a sales report can read the payroll one, so each entry in `config/reports.php` carries its OWN permission. This list is a convenience; the enforcement is in the runner, which checks again.\n\n`max_rows` is returned so a screen can warn BEFORE somebody waits for a run that comes back cut short, and `retention_days` so a file that has gone reads as expected rather than as a fault."
          }
        },
        {
          "name": "One Report",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports/{{reportKey}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "{{reportKey}}"
              ]
            },
            "description": "Its filters, with the options already resolved from tenant data.\n\nThe filters are the CONTRACT: what the screen renders, what the API narrows against, and what is captured onto every export. A report that honoured a key it never declared would produce a file whose own printed filter list does not explain it."
          }
        },
        {
          "name": "Run It",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"filters\": {\n    \"from\": \"2026-09-01\",\n    \"to\": \"2026-09-30\",\n    \"branch_id\": 1\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reports/{{reportKey}}/run",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "{{reportKey}}",
                "run"
              ]
            },
            "description": "The rows, on screen.\n\nAnything not in `filters()` is DROPPED rather than passed through, and a missing required filter is refused with the label a person would recognise.\n\n`truncated` is DECLARED, never left to be inferred by comparing the row count against a cap the caller may not know. A cut-off list that does not say so reads as \"that is everything\", and the total under it is then simply wrong.\n\n`filter_summary` is the filters IN WORDS — the same line the PDF prints, returned here so a screen shows what the file will say rather than composing its own and drifting from it."
          }
        },
        {
          "name": "Produce A File",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"format\": \"pdf\",\n  \"filters\": {\n    \"from\": \"2026-09-01\",\n    \"to\": \"2026-09-30\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reports/{{reportKey}}/export",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "{{reportKey}}",
                "export"
              ]
            },
            "description": "Runs it and keeps the file.\n\nTHE FILTERS ARE CAPTURED ONTO THE EXPORT, the same rule as a tax rate on an invoice line. A file handed to somebody in March has to still be explainable in November, and \"the sales report\" does not explain it — which branch and which dates do. Re-running produces a NEW export; it never restates an old one. The report's LABEL is captured too, so renaming it next year cannot rewrite what a file already sent was called.\n\nPDF carries the shop's header, the filters and a generated-at stamp because it will be read out of context months later. CSV deliberately carries none of that: a spreadsheet with three preamble rows above the headings is one nobody can pivot."
          }
        },
        {
          "name": "The Export Log",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports/exports?status=ready",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "exports"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "ready"
                }
              ]
            },
            "description": "Who took what out of the building, with which filters (M13).\n\nTHE ROW OUTLIVES THE FILE. Housekeeping removes files past their retention window and marks the row `expired`; deleting the row with the file would erase the only answer to \"who took the customer list out of the building in March\".\n\n`downloadable` is decided by the DATE, not the status — a missed sweep must not leave a stale file reachable just because nothing got round to restamping the row. Same rule as a store credit layer."
          }
        },
        {
          "name": "Download One",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports/exports/{{reportExportId}}/download",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "exports",
                "{{reportExportId}}",
                "download"
              ]
            },
            "description": "The bytes.\n\nThe files are PRIVATE — a report is the most concentrated view of a shop's trading there is, and a public disk puts it one guessed URL away. So this is an authenticated request like any other rather than a link the browser follows.\n\nAn expired export answers 404 with what actually happened, because \"the record is here and the file has gone\" is a different thing from \"not found\"."
          }
        },
        {
          "name": "Scheduled Reports",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports/schedules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "schedules"
              ]
            },
            "description": "Reports that send themselves.\n\n`consecutive_failures` is what makes a QUIET schedule visible. A failed run records its reason and moves the clock on rather than halting: a schedule that stopped on its first bad night would go quiet, and quiet is how a monthly pack goes missing for a quarter."
          }
        },
        {
          "name": "Schedule One",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Monthly finance pack\",\n  \"report_key\": \"finance.income-expense\",\n  \"format\": \"pdf\",\n  \"cadence\": \"monthly\",\n  \"recipients\": [\n    \"owner@example.com\"\n  ],\n  \"filters\": {\n    \"branch_id\": 1\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reports/schedules",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "schedules"
              ]
            },
            "description": "THE PERIOD IS NOT STORED. A schedule captures every filter EXCEPT the dates, which are derived from the cadence at each run — a monthly pack covers the month that has just ended. Storing them makes a schedule created in March send March's figures every month afterwards: a report that looks entirely correct and is a year out, which nobody catches because the numbers are plausible.\n\nScheduling is separately permissioned from running, and the report's own permission is checked as well: somebody who may not run a report must not be able to have it emailed to them nightly.\n\nThe recipients are STAFF addresses. Marketing consent governs contacting a CUSTOMER, and a shop emailing its own owner a sales pack is not that."
          }
        },
        {
          "name": "Change A Schedule",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"recipients\": [\n    \"owner@example.com\",\n    \"manager@example.com\"\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{baseUrl}}/reports/schedules/{{reportScheduleId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "schedules",
                "{{reportScheduleId}}"
              ]
            },
            "description": "Changing the CADENCE resets when it next runs; changing the recipients does not reach back into files already sent, because where a pack went is frozen onto the export — the same rule as `notification_log` freezing its recipient."
          }
        },
        {
          "name": "Stop A Schedule",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports/schedules/{{reportScheduleId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "schedules",
                "{{reportScheduleId}}"
              ]
            },
            "description": "Nobody on it receives the report again. The files it has already produced stay in the export log, because they left the building and that is a fact about the past."
          }
        },
        {
          "name": "Send It Now",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              },
              {
                "key": "Idempotency-Key",
                "value": "{{$guid}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/reports/schedules/{{reportScheduleId}}/run",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "reports",
                "schedules",
                "{{reportScheduleId}}",
                "run"
              ]
            },
            "description": "Produces and sends it without waiting for the clock — for somebody who wants the pack today, and for proving a new schedule works before trusting it to a month of silence.\n\nCarries an `Idempotency-Key`: a retried request must not put two files in front of the same people."
          }
        }
      ]
    },
    {
      "name": "Retail Dashboards (T072)",
      "description": "Screens 13.1-13.2. THREE endpoints rather than one with a role parameter: an owner, a manager and a cashier are asking different questions, so they carry different permissions - one covering all three would mean a cashier's dashboard opened the owner's, which is every figure the business has. Every tile states what it excludes, and every response says as at when. The B15 report set needs no requests of its own: T071's POST /reports/{key}/run runs every one of them, which is the whole point of a registry.",
      "item": [
        {
          "name": "The Owner's",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/dashboards/owner",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "dashboards",
                "owner"
              ]
            },
            "description": "The whole business: takings today against yesterday, margin, the month so far, stock at cost, what is owed each way, and what needs reordering.\n\nEVERY TILE CARRIES ITS OWN `basis` — what the figure excludes. That is not decoration: a number whose exclusions are not stated gets compared against one with different exclusions, and \"sales today\" that quietly counted confirmed phone orders is a different figure from the one on the Z-reading.\n\nYesterday sits BESIDE today rather than as a percentage: a shop that was shut yesterday would show -100% and mean nothing.\n\nStock value comes from the LAYERS — what was paid for the units still on the shelf, never quantity times a \"current cost\". Money owed to the shop excludes store credit, which is a separate ledger pointing the other way and is never netted.\n\nThe most expensive read in the system, which is why it is asked for rather than fired at everybody who signs in. `dashboard/stats` is still the portal landing page."
          }
        },
        {
          "name": "The Manager's",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/dashboards/manager?branch_id=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "dashboards",
                "manager"
              ],
              "query": [
                {
                  "key": "branch_id",
                  "value": "1"
                }
              ]
            },
            "description": "One branch: what it took, how many tills are open, what is waiting to be fulfilled, and what it needs to reorder.\n\nThe branch is ASKED for. Head office are exactly the people with no branch pinned, so with none given the response says `needs_branch` and shows nothing — quietly answering for the whole chain under a heading that says one shop is how somebody acts on the wrong figure. Same rule as the order desk asking which branch will fulfil an order.\n\nDeliberately not the owner's dashboard scoped down: a manager is asking a different question, and the tiles are different ones."
          }
        },
        {
          "name": "The Cashier's",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{accessToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/dashboards/cashier",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "dashboards",
                "cashier"
              ]
            },
            "description": "Their own open session, and nothing about the business.\n\nEXPECTED CASH IS DELIBERATELY ABSENT. It is derived, and it belongs on the X-reading where the count that proves it is taken — putting the figure on a dashboard invites somebody to type it into the count, and a variance that agrees by construction has told nobody anything. The response says so rather than leaving its absence to be noticed.\n\nWith no till open the response says that too, instead of an empty dashboard that reads as a broken screen."
          }
        }
      ]
    },
    {
      "name": "Shop - Public Storefront (T074)",
      "description": "The /api/shop/* namespace (Phase 8) - served to ANONYMOUS BROWSERS on the shop's own domain. Nothing from the staff chain applies: no X-Tenant, no bearer token, no session. The tenant.domain middleware resolves the hostname against an exact-match allowlist and answers every kind of refusal with one indistinguishable 404. Rate limited by host AND ip (throttle:shop). Account and checkout routes gain auth:shop and tighter buckets in T076/T079.",
      "item": [
        {
          "name": "Shop Context (Theme)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/theme",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "theme"
              ]
            },
            "description": "Everything the storefront needs before it can paint: the shop's name, the theme tokens (emitted verbatim as --shop-* CSS custom properties), and the locales. The SHAPE is the contract — T081's theme manager changes where the values come from, never what a deployed storefront receives.\n\nPUBLIC, and resolved by HOSTNAME: `X-Shop-Host` carries the address the customer is on (the storefront's API calls arrive on the API's origin, not the shop's), matched against an exact-match allowlist — the tenant's custom domain, or one label in front of the configured platform suffix. Nothing is ever parsed out of an arbitrary hostname.\n\nEVERY NO IS THE SAME NO. An unknown host, a suspended tenant and a switched-off shop module all answer an identical 404 — distinguishable refusals on a public namespace are a tenant enumeration oracle.\n\nSet {{shopHost}} to a tenant's domain (tenants.domain) or {slug}.{SHOP_PLATFORM_DOMAIN}, e.g. acme.localhost in development.\n\nSTOREFRONT UPLIFT (additive, 2026-09-03): `contact` {phone, email, address}, `social` (provider => url, WhatsApp as digits), `trust_points` (up to four {title, text}), `delivery_note`, and `payment_methods` — the checkout's own list (T078), carried here so a page render costs one call fewer. Every key is present and empty on an unconfigured shop; nothing here defaults to a policy the shop did not type."
          }
        },
        {
          "name": "Catalogue - Home",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/catalogue/home",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "catalogue",
                "home"
              ]
            },
            "description": "The front page's rails: what the shop chose to feature, and what is newest. Only LIVE listings — published AND catalogue-active — ever appear anywhere under /catalogue.\n\nCacheable (public, max-age=120) with `Vary: X-Shop-Host`: the tenant is IN the cache key, or a shared cache serves one shop's catalogue under another's domain."
          }
        },
        {
          "name": "Catalogue - Navigation",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/catalogue/nav",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "catalogue",
                "nav"
              ]
            },
            "description": "The category tree, PRUNED to what is actually on the website: a category with nothing published is not offered, and a populated category's ancestors survive the pruning — or the path to the products disappears with them. Counts are per-subtree."
          }
        },
        {
          "name": "Catalogue - Listing",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/catalogue/products?q=tea&sort=latest&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "catalogue",
                "products"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "tea"
                },
                {
                  "key": "sort",
                  "value": "latest"
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "Paged BY PRODUCT, never by variant — one product's variants must not split across pages (the Screen 4.1 rule). Filters: category slug (subtree included), brand_id, q, min/max_price, sort.\n\nSEARCH is tokenised AND-of-LIKE with wildcards escaped — a % in the query is a literal, never \"match everything\". A search that finds nothing is retried ONCE with each term's tail trimmed (the phone-keyboard typo), and the response says `corrected: true` — a silently widened search reads as the catalogue containing things it does not.\n\nAvailability on every card is a BUCKET (in | low | out) from AVAILABLE stock, never a number and never on_hand. `category_name` carries the display name so a page never renders a slug at a person.\n\nThe price band and price sort work on the variants' own price columns (web_price, else retail) — an APPROXIMATION, declared in the service, because dated prioritised price lists cannot be pushed into a SQL sort. The DISPLAYED price is always the resolved one."
          }
        },
        {
          "name": "Catalogue - Product Detail",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/catalogue/products/{{shopProductSlug}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "catalogue",
                "products",
                "{{shopProductSlug}}"
              ]
            },
            "description": "One product: web copy (overrides falling back to the product's own words), attributes, and the variant matrix — each ACTIVE variant with its resolved price and its availability bucket.\n\nPRICES come from the one resolver (channel `web`, batched). Where no list matched and the variant carries a typed `web_price`, that wins over retail — a shop that typed one meant it for exactly this channel.\n\nUnknown, unpublished and catalogue-deactivated slugs all answer the same 404. Carries an ETag; If-None-Match answers 304."
          }
        },
        {
          "name": "Cart - Show",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Cart-Token",
                "value": "{{cartToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/cart",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "cart"
              ]
            },
            "description": "The basket, priced NOW. The cart holds nothing and freezes nothing (D4): every read re-resolves prices through the one resolver and re-derives the availability buckets, and there is deliberately no price column on a cart line — a stored cart price is a second answer waiting to disagree with the shelf. Never cached; personal responses carry no cache headers at all.\n\nThe X-Cart-Token is the anonymous basket's identity — 64 random characters issued by the first add. A spent (checked-out) token resolves to null, so a stale browser cannot resurrect a basket the shop now owns as an order."
          }
        },
        {
          "name": "Cart - Add Line",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Cart-Token",
                "value": "{{cartToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/cart/lines",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "cart",
                "lines"
              ]
            },
            "description": "Adds to the basket — and holds NOTHING. Only what is on the website can be carted: the add goes through the same scopeLive() the catalogue browses with, so a variant somebody unpublished mid-session refuses rather than sailing through to a checkout that sells what the shop took down. Adding the same variant again grows the ONE line, capped at 999.\n\nOmit X-Cart-Token on the first add; the response carries the new token.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_variant_id\": 1,\n  \"quantity\": \"2\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cart - Change Line",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Cart-Token",
                "value": "{{cartToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/cart/lines/{{cartLineId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "cart",
                "lines",
                "{{cartLineId}}"
              ]
            },
            "description": "Sets a line's quantity. The response is the whole re-priced basket.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"quantity\": \"3\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Cart - Remove Line",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Cart-Token",
                "value": "{{cartToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/cart/lines/{{cartLineId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "cart",
                "lines",
                "{{cartLineId}}"
              ]
            },
            "description": "Takes a line out. The response is the whole re-priced basket."
          }
        },
        {
          "name": "Checkout - Place Order",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Cart-Token",
                "value": "{{cartToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/checkout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "checkout"
              ]
            },
            "description": "THE COMMITMENT POINT (D4). One transaction: the customer is linked by DIGITS or EMAIL, never name (a guest typing the number a till already knows becomes THAT customer's order); the cart's lines are priced by the server at this instant; the order is created and CONFIRMED through the same engine as the order desk — promotions applied, delivery refused without an address, and every line's stock held, ALL OR NONE. A shortage answers 422 with the engine's own words naming the line and what is available now, and rolls everything back: no order, no holds, the cart intact.\n\nNOTHING IN THIS PAYLOAD CAN SET A PRICE — there is no field for one, and a smuggled `unit_price` is discarded by validation.\n\nThe order confirms with its holds INDEFINITE; the TTL lives on the sweep (shop:expire-checkouts), which CANCELS unpaid web orders past shop.checkout_ttl_minutes and never touches an order with money on it.\n\nThrottled tighter than browsing (throttle:shop-checkout): each attempt can reserve the last unit of something, so a hammered checkout is a denial-of-stock attack.\n\nMARKETING CONSENT (T083, C10): `marketing_opt_in` is OPTIONAL and unticked by default. Absent means the shop never ASKED and nothing is written — inventing a refusal for a question nobody put would put a fact in the consent history that nobody established. Present-and-false IS recorded, because \"we offered it and they left it unticked\" is exactly the evidence that answers whether the box was pre-ticked. Either way the default is no: hasConsented() reads the latest row and a missing row is a refusal.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"customer\": {\n    \"name\": \"Nimal Perera\",\n    \"phone\": \"0771234567\",\n    \"email\": \"nimal@example.com\"\n  },\n  \"fulfilment_method\": \"delivery\",\n  \"address\": {\n    \"line1\": \"12 Galle Road\",\n    \"city\": \"Colombo\",\n    \"postal_code\": \"00300\"\n  },\n  \"notes\": \"Please call before delivering.\",\n  \"marketing_opt_in\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Order - Show",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Order-Token",
                "value": "{{orderToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/orders/{{orderReference}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "orders",
                "{{orderReference}}"
              ]
            },
            "description": "The customer's own order — THE RESULT PAGE ONLY READS (D5). Whatever the gateway's redirect claimed, the state answered here is what the WEBHOOK wrote: a browser can close, lie or replay, and none of it moves an order.\n\nAuthenticated by the order's `web_token` (minted at checkout, returned once in the checkout response) in the X-Order-Token HEADER — never a URL, where it would leak through logs and Referer. Reference and token must BOTH match and the refusal never says which was wrong.\n\n`payment_options` lists the gateways usable right now, and only while something is still owed — a paid order showing payment buttons invites paying twice. Never cached."
          }
        },
        {
          "name": "Order - Start Payment",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Order-Token",
                "value": "{{orderToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/orders/{{orderReference}}/payments",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "orders",
                "{{orderReference}}",
                "payments"
              ]
            },
            "description": "Opens a HOSTED payment (D5): PayHere answers a form the browser POSTs to the gateway's own page, Stripe a redirect URL to Stripe Checkout. Card data never touches this API — hosted flows only, which keeps PCI scope at SAQ-A and is non-negotiable.\n\nThe amount is the order's outstanding balance, computed SERVER-SIDE at this instant; nothing in the payload can influence it. Each initiation is its own attempt (`{order}-P01`, `-P02`, ...) with the C6 state machine: pending → paid | failed | expired, decided by the WEBHOOK and nothing else.\n\nShares the checkout's tight throttle bucket: each call can open gateway sessions, and each session costs real work.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"provider\": \"payhere\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Webhook - Gateway Notify",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/webhooks/{{gatewayProvider}}/{{tenantSlug}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "webhooks",
                "{{gatewayProvider}}",
                "{{tenantSlug}}"
              ]
            },
            "description": "THE SOURCE OF TRUTH (D5/B9). The gateway calls the URL it was given at initiation; the tenant travels in the PATH because no X-Shop-Host arrives. Every rule of B9 is enforced behind this endpoint:\n\n- the RAW payload is persisted BEFORE processing, and can be replayed from the stored row;\n- the signature is verified against the resolved tenant's own gateway secret (PayHere md5sig, Stripe HMAC v1) — a guessed tenant slug buys nothing;\n- processing is idempotent by event id, and the paid transition is ONE conditional UPDATE — a replayed delivery answers `duplicate`, a SUCCESS, exactly like the POS sync verdict;\n- the amount is verified server-side against what the shop asked the gateway to charge; a mismatch PARKS in the exception queue and confirms NOTHING — money that arrived is never dropped on the floor;\n- a capture lands in `payments` as method `gateway`, which is what T070's gateway reconciliation stream reads.\n\nThe body is the gateway's own format (PayHere form-encoded, Stripe JSON with a Stripe-Signature header); this documented request is a placeholder — real deliveries come from the gateway.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"example\": \"the gateway's own payload format\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Payment Methods",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/payment-methods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "payment-methods"
              ]
            },
            "description": "How a customer may choose to pay HERE (T078). Server-side, and that is the point: `cod` commits the shop to sending goods it has not been paid for and `bank_transfer` to publishing account details, so neither can be opted into by a hand-made request at a shop that has not switched it on. `gateway` is the fallback and always allowed — it commits the shop to nothing: the order simply waits.\n\nBank transfer is listed only when it is USABLE: enabled AND carrying somewhere to send the money. An account panel with nothing in it asks somebody to pay into a void."
          }
        },
        {
          "name": "Newsletter - Subscribe",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"reader@example.com\"\n}"
            },
            "url": {
              "raw": "{{baseUrl}}/shop/newsletter",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "newsletter"
              ]
            },
            "description": "The newsletter box in the storefront footer (storefront uplift). A subscription is a CONSENT ROW on the customer register - channel email, purpose marketing, source `web_newsletter` - never a mailing list of its own, so the campaign sender's per-recipient check (T058) is the only answer to \"may we email this person\".\n\nONE ANSWER whatever the truth: known address or new, subscribed already or not. Saying \"you are already subscribed\" would tell a stranger the address belongs to a customer here, which is the T079 rule for register and forgot-password. On the `shop-auth` throttle bucket for the same reason.\n\nAn address the till does not know becomes a customer named by the address itself - the form asks for nothing else, and inventing a name would be exactly that."
          }
        },
        {
          "name": "Order - Send Bank Slip",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "X-Order-Token",
                "value": "{{orderToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/orders/{{orderReference}}/slips",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "orders",
                "{{orderReference}}",
                "slips"
              ]
            },
            "description": "The customer's CLAIM that they transferred the money, with the photograph (T078). IT MOVES NOTHING. Until a person has compared it with the bank the shop has been told a story, not given money — so no `payments` row is written, `paid_total` does not change, and the order still cannot dispatch.\n\nThe image is stored as an ordinary PRIVATE attachment: double-checked types, forced download disposition, short-lived signed links. A bank slip carries an account number and a name and must never be servable off a plain path.\n\nONE SLIP AT A TIME. A second submission while one is awaiting a decision is refused; a REJECTED slip may be replaced, which is what rejection is for — \"send us a clearer one\".\n\nMultipart. Throttled with uploads, because it writes a file.",
            "body": {
              "mode": "formdata",
              "formdata": [
                {
                  "key": "slip",
                  "type": "file",
                  "src": [],
                  "description": "jpg, png, webp, heic or pdf"
                },
                {
                  "key": "reference",
                  "value": "TRF-99881",
                  "type": "text"
                },
                {
                  "key": "amount",
                  "value": "950.00",
                  "type": "text",
                  "description": "What they say they sent. The verifier corrects it."
                },
                {
                  "key": "bank_name",
                  "value": "Commercial Bank",
                  "type": "text"
                },
                {
                  "key": "paid_on",
                  "value": "2026-09-01",
                  "type": "text"
                }
              ]
            }
          }
        },
        {
          "name": "Account - Register",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/register",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "register"
              ]
            },
            "description": "Creates a web login — or appears to. THE ANSWER IS THE SAME WHATEVER THE TRUTH IS (D10). An endpoint that distinguished a known address from an unknown one would be a customer list readable one address at a time. What actually happened is told to the EMAIL, which only reaches the person who owns it.\n\nAN UNVERIFIED CREDENTIAL IS A CLAIM, NOT AN ACCOUNT: it cannot sign in, and a later registration for the same address REPLACES it. Without that, typing a stranger's address once would lock them out of ever registering. A VERIFIED account is never touched — the owner is emailed instead, with the reset link they would want if it was them.\n\nLINKS BY EMAIL, NEVER BY PHONE. An existing customer record is adopted only on the address, because verification proves the registrant reads it. Matching on a phone number would hand over somebody else's purchase history for the price of typing their number; where only a phone matches, a second record is created and the audited merge resolves it with a person looking.\n\nMARKETING CONSENT (T083, C10): `marketing_opt_in` is OPTIONAL and unticked by default. Absent means the shop never ASKED and nothing is written — inventing a refusal for a question nobody put would put a fact in the consent history that nobody established. Present-and-false IS recorded, because \"we offered it and they left it unticked\" is exactly the evidence that answers whether the box was pre-ticked. Either way the default is no: hasConsented() reads the latest row and a missing row is a refusal.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Nimal Perera\",\n  \"email\": \"nimal@example.com\",\n  \"phone\": \"0771234567\",\n  \"password\": \"Str0ng-Passw0rd!\",\n  \"password_confirmation\": \"Str0ng-Passw0rd!\",\n  \"marketing_opt_in\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Verify Email",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "verify"
              ]
            },
            "description": "Proves the address, which is what turns a claim into an account. The token carries the EMAIL it was issued for, so a link stops working the moment the address on the credential changes — it cannot verify an address nobody asked about. Tenant-bound: a token signed for another shop is refused.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{shopVerifyToken}}\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Login",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/login",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "login"
              ]
            },
            "description": "Mints a token on the `shop` guard. A staff token can never authenticate here and this one can never reach the staff API: jwt-auth stamps the provider into every token and each guard checks its own.\n\nONE REFUSAL for a wrong address and a wrong password alike (401). The one place a difference is allowed is AFTER a correct password: telling somebody who has just proved they own the account that it still needs verifying (403) leaks nothing they did not know.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"nimal@example.com\",\n  \"password\": \"Str0ng-Passw0rd!\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Forgot Password",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/forgot-password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "forgot-password"
              ]
            },
            "description": "Asks for a reset link. THE ANSWER IS THE SAME WHATEVER THE TRUTH IS (D10). An endpoint that distinguished a known address from an unknown one would be a customer list readable one address at a time. What actually happened is told to the EMAIL, which only reaches the person who owns it.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"nimal@example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Reset Password",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/reset-password",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "reset-password"
              ]
            },
            "description": "Sets a new password. The token carries a FINGERPRINT of the password it was issued against, so using it once kills it — the hash changes and the fingerprint stops matching. That is single-use without a table of tokens to sweep, and it means an old link left in a mailbox cannot undo a later change.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"token\": \"{{shopResetToken}}\",\n  \"password\": \"A-New-Passw0rd!\",\n  \"password_confirmation\": \"A-New-Passw0rd!\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Logout",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/logout",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "logout"
              ]
            },
            "description": "Invalidates the token.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Profile",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account"
              ]
            },
            "description": "Who they are, what the SHOP owes them (store credit — a separate ledger, never netted against what they owe it), and their marketing preferences. Never cached."
          }
        },
        {
          "name": "Account - Update Profile",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account"
              ]
            },
            "description": "Name and phone. The EMAIL is deliberately not editable here: it is the account's identity and the thing verification proved, so changing it is a re-verification flow rather than a profile edit — otherwise a verified account could be moved onto an address nobody has proved they read.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Nimal Perera\",\n  \"phone\": \"0771234567\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Preferences",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/preferences",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "preferences"
              ]
            },
            "description": "Marketing consent, APPEND-ONLY (B16). A change of mind is a NEW row saying when it was decided and how it was obtained; editing the last one would destroy the evidence that somebody ever said yes, which is the only thing that makes sending defensible. Service messages about their own orders are never affected.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"preferences\": {\n    \"email\": true,\n    \"sms\": false\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Orders",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/orders",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "orders"
              ]
            },
            "description": "THE CHAIN-WIDE REGISTER EARNING ITS KEEP: the customer's orders through every channel, so a purchase rung up at a till appears here beside a web one — they are one customer record. Branch-scope is lifted on purpose: a customer is not a branch's property and shops where they like."
          }
        },
        {
          "name": "Account - Addresses",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/addresses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "addresses"
              ]
            },
            "description": "The customer's own address book, reused by checkout."
          }
        },
        {
          "name": "Account - Add Address",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/addresses",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "addresses"
              ]
            },
            "description": "Adds one to the book.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"label\": \"Home\",\n  \"line1\": \"12 Galle Road\",\n  \"city\": \"Colombo\",\n  \"postal_code\": \"00300\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Account - Remove Address",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopAccountToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/addresses/{{addressId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "addresses",
                "{{addressId}}"
              ]
            },
            "description": "Removes one. Somebody else's address is NOT FOUND rather than forbidden — a 403 would confirm the row exists."
          }
        },
        {
          "name": "Pages - Footer Menu",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/pages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "pages"
              ]
            },
            "description": "The shop's own pages that carry a footer position. A page WITHOUT one stays reachable by its address but stays off the menu — which is how a privacy notice lives at a stable URL without cluttering every page."
          }
        },
        {
          "name": "Pages - Read",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/pages/{{pageSlug}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "pages",
                "{{pageSlug}}"
              ]
            },
            "description": "One static page. The body was SANITISED WHEN IT WAS SAVED, so what leaves here is already free of script, iframes, event handlers and javascript: URLs — one place owns the rule and nothing downstream has to remember. Unpublished pages 404 like anything else that is not on the website."
          }
        },
        {
          "name": "Crawler - Sitemap",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "*/*"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/sitemap.xml",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "sitemap.xml"
              ]
            },
            "description": "XML, not JSON: the thing fetching this is Googlebot, not the storefront. The SSR server proxies it so it answers on the shop's OWN domain, where a crawler looks for it.\n\nLISTS ONLY WHAT IS LIVE. `scopeLive()` is the one definition of \"on the website\", so unpublishing a product stops advertising it in the same act — a sitemap naming a URL that 404s teaches a search engine to distrust the whole file. A RETIRED slug is absent too: it redirects, and spending a crawl on a 301 is waste rather than value.\n\nCarries `Vary: X-Shop-Host`, because a shared cache serving one shop's sitemap on another shop's domain is the multi-tenant leak one layer below the data."
          }
        },
        {
          "name": "Crawler - Robots",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "*/*"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/robots.txt",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "robots.txt"
              ]
            },
            "description": "Text, and per tenant. A shop with NOTHING published answers `Disallow: /` — a coming-soon page is not what a new domain should spend its first impression on. An open shop allows the catalogue and keeps /cart, /checkout, /account and /order out of the index: they are personal, and some of them cost real work to answer."
          }
        },
        {
          "name": "Account - Privacy requests",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/privacy",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "privacy"
              ]
            },
            "description": "What this customer has asked about their own data (T083, C10), and where each one got to.\n\nBOTH ASKS ARE ANSWERED BY A PERSON. An export contains one named customer's whole file and an erasure rewrites what every document about them says, so neither is something a session token alone should be able to make happen. What the customer gets immediately is a receipt for having asked, which is the thing they can hold the shop to."
          }
        },
        {
          "name": "Account - Ask for data / erasure",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/privacy",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "privacy"
              ]
            },
            "description": "Records a PDPA request (T083, C10). `type` is export or erasure.\n\nASKING TWICE IS NOT TWO REQUESTS: a standing pending request of the same kind is returned rather than duplicated, because two bundles about one person in the world at once is exactly what this prevents. An erasure and an export are different questions and stand on their own.\n\nAN ERASURE IS ANONYMISATION, NOT DELETION, and the pack says so. A customer with sales cannot be hard-deleted without destroying the shop's own books.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"type\": \"export\",\n  \"note\": \"Please send everything you hold.\"\n}"
            }
          }
        },
        {
          "name": "Account - Read my bundle",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/privacy/{{privacyRequestId}}/download",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "privacy",
                "{{privacyRequestId}}",
                "download"
              ]
            },
            "description": "The export bundle, once staff have generated it.\n\nScoped to the caller's own customer, and gated on the EXPIRY rather than the status: the DATE decides, so a missed retention sweep can never leave a file about a named person fetchable for ever. Someone else's request answers 404, indistinguishable from one that does not exist."
          }
        },
        {
          "name": "Catalogue - Product Reviews",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/catalogue/products/{{shopProductSlug}}/reviews?page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "catalogue",
                "products",
                "{{shopProductSlug}}",
                "reviews"
              ],
              "query": [
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "Approved reviews of a product on the website, newest first, ten a page, with the summary (average, count, how the stars fall). APPROVED ONLY: a review reaches the shop window after a person has read it (storefront uplift, second pass). Cacheable like the rest of the catalogue."
          }
        },
        {
          "name": "Account - My Reviews",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopCustomerToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/reviews",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "reviews"
              ]
            },
            "description": "The signed-in customer's own reviews, whatever their status, with the written reason for a rejection."
          }
        },
        {
          "name": "Account - Write a Review",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "X-Shop-Host",
                "value": "{{shopHost}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{shopCustomerToken}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/account/reviews",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "account",
                "reviews"
              ]
            },
            "description": "Write (or, while pending or rejected, replace) the signed-in customer's review of a product. Refused against an approved one. `verified` is DERIVED from the order register - a completed order through ANY channel holding one of the product's variants - never claimed. The name shown is a snapshot ('Nimal P.'), which an erasure masks.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"product_slug\": \"{{shopProductSlug}}\",\n  \"rating\": 5,\n  \"title\": \"Good\",\n  \"body\": \"Lovely tea, would buy again.\"\n}"
            }
          }
        },
        {
          "name": "Webhook - Mintpay Browser Return (GET)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "text/html"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/shop/webhooks/{{gatewayProvider}}/{{tenantSlug}}?orderId={{shopPaymentReference}}&hash={{mintpayReturnHash}}&rt={{shopOrigin}}/order/{{shopOrderReference}}?payment=done&rc={{shopOrigin}}/order/{{shopOrderReference}}?payment=cancelled",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "shop",
                "webhooks",
                "{{gatewayProvider}}",
                "{{tenantSlug}}"
              ],
              "query": [
                {
                  "key": "orderId",
                  "value": "{{shopPaymentReference}}"
                },
                {
                  "key": "hash",
                  "value": "{{mintpayReturnHash}}"
                },
                {
                  "key": "rt",
                  "value": "{{shopOrigin}}/order/{{shopOrderReference}}?payment=done"
                },
                {
                  "key": "rc",
                  "value": "{{shopOrigin}}/order/{{shopOrderReference}}?payment=cancelled"
                }
              ]
            },
            "description": "Mintpay has no server-to-server webhook: it sends the CUSTOMER'S BROWSER to one of two URLs this server signed at initiation - a success URL carrying HMAC-SHA256(merchant_id + amount + attempt reference, secret) or a failure URL carrying HMAC-SHA256(attempt reference, secret), each base64-encoded in `hash` (storefront uplift, second pass). The same path answers a GET, verifies the signed QUERY exactly as it would a body, writes the verdict, and then redirects the person to the order page named in `rt` (success) or `rc` (cancel) - but only when that address is on one of this tenant's own hostnames; anything else goes to the shop's front door. The order page READS the truth as always; the browser confirms nothing.\n\nPayHere, Stripe and Koko keep POSTing to the same path and get the JSON verdict."
          }
        }
      ]
    },
    {
      "name": "Web Shop - Payments Staff (T077)",
      "description": "The STAFF side of online payments, at /api/webshop — deliberately not /api/shop, which never passes through the staff middleware chain (D10). Full chain plus per-route permissions: shop.payments.view reads, shop.payments.manage replays and resolves, shop.gateways.manage holds the credentials.",
      "item": [
        {
          "name": "Gateways - List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/gateways",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "gateways"
              ]
            },
            "description": "Both providers with their configuration — SECRETS MASKED. A stored secret never leaves the server again: it renders as ••••••••, and saving that mask back keeps what is stored. Viewing is gated with EDITING (shop.gateways.manage): a read-only look at merchant credentials is not a harmless read."
          }
        },
        {
          "name": "Gateways - Save",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/gateways/{{gatewayProvider}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "gateways",
                "{{gatewayProvider}}"
              ]
            },
            "description": "Credentials in, never out — encrypted at rest like a tenant's DB password. `test_mode` defaults TRUE: a gateway that defaults to live charges a real card during a demo. Enabling without full credentials saves but warns, and the gateway is not offered until it is usable.\n\nRequires shop.gateways.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"enabled\": true,\n  \"test_mode\": true,\n  \"config\": {\n    \"merchant_id\": \"M12345\",\n    \"merchant_secret\": \"sandbox-secret\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Payments - List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/payments?state=pending",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "payments?state=pending"
              ]
            },
            "description": "Gateway payment attempts — the C6 state machine, one row per initiation. Filter by `state` (pending | paid | failed | expired) and `provider`. The money itself lives in `payments` (method `gateway`); these rows are the story of each attempt, kept whatever happened to it.\n\nRequires shop.payments.view."
          }
        },
        {
          "name": "Webhooks - List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/webhooks?status=parked",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "webhooks?status=parked"
              ]
            },
            "description": "The webhook log — and `status=parked` is THE EXCEPTION QUEUE (D5): money that arrived and could not be confirmed (amount tampered, order cancelled under a slow payer, an attempt this system never made), waiting for a person. The screen opens on it.\n\nList rows omit the raw payload; the detail endpoint carries it.\n\nRequires shop.payments.view."
          }
        },
        {
          "name": "Webhooks - Show",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/webhooks/{{webhookId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "webhooks",
                "{{webhookId}}"
              ]
            },
            "description": "One delivery, RAW PAYLOAD INCLUDED — the gateway's own words, frozen at receipt. The payload, provider and signature can never be rewritten and the row refuses deletion; only the processing outcome moves.\n\nRequires shop.payments.view."
          }
        },
        {
          "name": "Webhooks - Replay",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/webhooks/{{webhookId}}/replay",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "webhooks",
                "{{webhookId}}",
                "replay"
              ]
            },
            "description": "Re-runs a delivery from its STORED payload (D5's replayability) — only a parked or failed row, and only one whose signature verified at receipt; a payload nobody signed is investigated, not replayed. Processing is idempotent, so money already captured answers `duplicate`.\n\nRequires shop.payments.manage."
          }
        },
        {
          "name": "Webhooks - Resolve",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/webhooks/{{webhookId}}/resolve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "webhooks",
                "{{webhookId}}",
                "resolve"
              ]
            },
            "description": "Closes a parked delivery with a WRITTEN reason — mandatory, because an unexplained resolution is indistinguishable from an unnoticed one (the courier-settlement rule, pointed at money). Records who and when.\n\nRequires shop.payments.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Refunded at the gateway console; customer notified.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Slips - Queue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/slips?status=submitted",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "slips?status=submitted"
              ]
            },
            "description": "THE VERIFICATION QUEUE (T078). A submitted slip is a customer who believes they have paid and an order standing still — the list somebody must work. Each row carries what was CLAIMED alongside what the order actually owes, and the id of the slip image (fetched through the ordinary private-attachment endpoints).\n\nRequires shop.payments.view."
          }
        },
        {
          "name": "Slips - Verify",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/slips/{{slipId}}/verify",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "slips",
                "{{slipId}}",
                "verify"
              ]
            },
            "description": "A person checked the bank and SIGNED for it. Only now is a `payments` row written (method `bank_transfer`, the verifier recorded as who took it) and `paid_total` re-projected — through the same helper as a gateway capture, so there is one place that records money reaching a web order.\n\nTHE AMOUNT IS THE VERIFIER'S. The bank statement is the fact and the customer's typing is a claim, so a short payment leaves the order PART-PAID rather than silently marked settled — and the dispatch guard still holds the goods. Omit it to accept what was claimed.\n\nDUAL CONTROL: whoever raised the order may not verify its payment. Vacuous for a web order (a customer raised it) and exactly the point for a desk one.\n\nRequires shop.slips.verify — its own permission, because replaying a webhook re-runs a decision the GATEWAY made, while this is a person deciding money arrived on a photograph.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"amount\": \"950.00\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Slips - Reject",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/slips/{{slipId}}/reject",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "slips",
                "{{slipId}}",
                "reject"
              ]
            },
            "description": "No, and WHY. The reason is mandatory and the CUSTOMER IS TOLD IT as a service message: their order is standing still and they believe they have paid, so without a reason they wait for goods that will never move and the shop hears about it as a complaint instead of a reply.\n\nNothing is written to `payments`, and the customer may then send another slip — rejection is how a shop asks for a clearer one.\n\nRequires shop.slips.verify.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Nothing matching this reference reached the account.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Listings - Index",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/listings?state=off",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "listings?state=off"
              ]
            },
            "description": "What is on the website and what is not (Screen 10.1). `state=off` is the default view because it is the list somebody WORKS THROUGH when filling the shop — and a product with no listing row at all counts as off, which is the usual state for most of a catalogue.\n\nRequires shop.content.view."
          }
        },
        {
          "name": "Listings - Save Copy",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/listings/{{productId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "listings",
                "{{productId}}"
              ]
            },
            "description": "The web copy, the SEO fields and the ADDRESS. Renaming a published slug retires the old one, which the storefront then answers with a 301 for ever: breaking an indexed URL throws away search ranking the shop paid months to earn and breaks every link anybody shared.\n\nA slug is never reused — not by another product and not from the retired history, because one address quietly coming to mean a different product is worse than a 404. A DRAFT slug is not retired when it changes: nobody has linked to a draft, and retiring every keystroke would burn addresses that never existed.\n\nRequires shop.content.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"slug\": \"ceylon-tea-200g\",\n  \"seo_title\": \"Ceylon Tea 200g\",\n  \"seo_description\": \"Loose leaf, 200g.\",\n  \"web_description\": \"A fine tea.\",\n  \"featured\": true\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Listings - Publish",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/listings/{{productId}}/publish",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "listings",
                "{{productId}}",
                "publish"
              ]
            },
            "description": "PUTS IT IN FRONT OF THE PUBLIC. Its own permission, because deciding what the world sees is a different act from describing it.\n\nRefused for a product that is not ACTIVE in the catalogue: `scopeLive()` needs both, so publishing an inactive one would put a listing on a page that 404s — silently, which is the worst way for it to fail.\n\nThe catalogue cache is invalidated by a version bump, so it appears IMMEDIATELY: a shop that presses Publish and is told to wait cannot tell that from \"it did not work\".\n\nRequires shop.content.publish.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"slug\": \"ceylon-tea-200g\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Listings - Unpublish",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/listings/{{productId}}/unpublish",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "listings",
                "{{productId}}",
                "unpublish"
              ]
            },
            "description": "Off the website, INTACT in the portal. The slug, the copy and the retired address history all survive, so putting it back is one click rather than typing it all again and minting a new URL.\n\nRequires shop.content.publish.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Pages - Index",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/pages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "pages"
              ]
            },
            "description": "Every static page, draft and published. Requires shop.content.view."
          }
        },
        {
          "name": "Pages - Read",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/pages/{{pageId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "pages",
                "{{pageId}}"
              ]
            },
            "description": "One page WITH its body, for editing. The stored body is already sanitised. Requires shop.content.view."
          }
        },
        {
          "name": "Pages - Create",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/pages",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "pages"
              ]
            },
            "description": "A new static page. THE BODY IS SANITISED ON THE WAY IN: parsed by a real HTML parser and rebuilt from an allowlist, so script, iframes, event handlers, style and javascript:/data: URLs never reach storage, let alone a customer's browser. The TEXT inside a dropped element is kept — somebody pasting from a word processor should not lose their words because the wrapper was a <font>.\n\nA tenant admin is trusted with their own business and not with the session of every customer who reads their returns policy.\n\nRequires shop.content.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"slug\": \"returns\",\n  \"title\": \"Returns\",\n  \"status\": \"published\",\n  \"body\": \"<p>Return anything within 14 days.</p>\",\n  \"menu_order\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Pages - Update",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/pages/{{pageId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "pages",
                "{{pageId}}"
              ]
            },
            "description": "Same sanitising, same rules. Two pages cannot share one address.\n\nRequires shop.content.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"published\",\n  \"menu_order\": 2\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Pages - Delete",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/pages/{{pageId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "pages",
                "{{pageId}}"
              ]
            },
            "description": "Removes the page. UNLIKE a product address, a removed page keeps no redirect — a page is the shop's own words rather than a product anybody linked to from outside, and the screen says so before it happens.\n\nRequires shop.content.manage."
          }
        },
        {
          "name": "Theme - Read",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/theme",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "theme"
              ]
            },
            "description": "The saved theme, the API defaults it falls back to, and the CONTRAST AUDIT (Screen 10.2). The audit is on every read because a shop choosing its own colours will eventually choose a pale grey on white, and the person choosing is looking at an office monitor rather than a phone in the sun.\n\nRequires shop.content.view."
          }
        },
        {
          "name": "Theme - Save",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/theme",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "theme"
              ]
            },
            "description": "Brand tokens, the announcement bar, the hero, footer words and measurement IDs. Applied to the storefront WITHOUT A REBUILD: it reads them at SSR time, and the catalogue cache generation is bumped so the change is not sitting behind a page cached a moment earlier.\n\nTOKENS ARE MERGED OVER THE DEFAULTS, never replace them — a shop that sets two colours keeps working fonts and radius, and the deployed contract /api/shop/theme still answers with the whole set. A value carrying a brace or a semicolon is dropped: tokens are emitted into a style block, and those are how a value escapes one.\n\nANALYTICS ARE IDS. A pasted script tag is REFUSED with a message saying so, because accepting and silently ignoring it would leave somebody believing their measurement was running. The storefront builds the loader itself.\n\nA failing contrast pairing does NOT block the save: a brand is the shop's decision, and a hard block only teaches somebody to pick a nearly-passing colour they like less. The response says how many pairings are hard to read.\n\nRequires shop.content.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"tokens\": {\n    \"primary\": \"#8b1d3f\"\n  },\n  \"announcement\": \"Free delivery over 5000\",\n  \"announcement_enabled\": true,\n  \"hero_title\": \"Ceylon tea, direct\",\n  \"analytics\": {\n    \"ga4\": \"G-ABCD1234\"\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Banners - Index",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/banners",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "banners"
              ]
            },
            "description": "Every banner, with `showing_now` DERIVED from the clock alongside `enabled`. They are different questions, and only the derived one answers \"why can I not see my weekend banner on a Tuesday\".\n\nRequires shop.content.view."
          }
        },
        {
          "name": "Banners - Create",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/banners",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "banners"
              ]
            },
            "description": "Scheduled the way a PROMOTION is (B7): a date range is the coarse answer, and the day-of-week mask and time window are the ones a date range cannot express — without them a weekend banner needs one row per day. Monday is bit 0, so a weekend mask is 96, and a window from 22:00 to 02:00 is a night sale rather than an empty range.\n\nRequires shop.content.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"title\": \"Weekend sale\",\n  \"subtitle\": \"20% off tea\",\n  \"days_of_week\": 96,\n  \"starts_at\": \"09:00\",\n  \"ends_at\": \"21:00\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Banners - Update",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/banners/{{bannerId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "banners",
                "{{bannerId}}"
              ]
            },
            "description": "Same rules. A banner cannot stop before it starts.\n\nRequires shop.content.manage.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"enabled\": false\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          }
        },
        {
          "name": "Banners - Delete",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/banners/{{bannerId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "banners",
                "{{bannerId}}"
              ]
            },
            "description": "Removes it from the front page.\n\nRequires shop.content.manage."
          }
        },
        {
          "name": "Privacy - Queue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/privacy-requests?status=pending",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "privacy-requests?status=pending"
              ]
            },
            "description": "The PDPA queue (T083, C10). Requires shop.privacy.view.\n\nA QUEUE, not an endpoint that acts: a privacy request has a statutory clock on it, and something a person has to look at is the only shape that survives somebody being on leave. Pending sorts first, the same reason the reconciliation screen opens on its exceptions.\n\nEACH ERASURE ROW CARRIES `standing`: what the customer still has open with the shop — money owed, orders still to send. NOT a refusal and it blocks nothing: erasing a customer who owes money is a lawful choice a shop may make. It is the fact somebody needs in front of them, because an erasure taken in ignorance cannot be taken back. An export request carries null, because nobody is weighing anything."
          }
        },
        {
          "name": "Privacy - Generate export bundle",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/privacy-requests/{{privacyRequestId}}/export",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "privacy-requests",
                "{{privacyRequestId}}",
                "export"
              ]
            },
            "description": "Generates the bundle and starts a short-lived window on it. Requires shop.privacy.manage.\n\nThe file lands on the PRIVATE disk and the ROW OUTLIVES THE FILE: housekeeping (privacy:prune-exports) deletes the bundle and keeps the row, because it answers who took a copy of this person's file out of the building and when — which the file itself cannot once it is gone."
          }
        },
        {
          "name": "Privacy - Erase the customer",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/privacy-requests/{{privacyRequestId}}/erase",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "privacy-requests",
                "{{privacyRequestId}}",
                "erase"
              ]
            },
            "description": "Anonymises the person and keeps the books. Requires shop.privacy.erase — ITS OWN PERMISSION, because this is the only act in the application that cannot be undone.\n\nName, contacts, addresses, web login and the free text on every consent row go. What stays is the transactional skeleton — orders, lines, payments, stock movements, tax figures — which the shop is required to keep and which names nobody afterwards. The CONSENT ROWS THEMSELVES STAY: they are the proof of what was asked and answered, and destroying them would leave the shop unable to show it ever had permission to write to anybody.\n\n`confirm` is required in the body rather than the URL alone: a mis-click on a list of names is exactly how this would happen by accident.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"confirm\": true\n}"
            }
          }
        },
        {
          "name": "Privacy - Refuse a request",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/privacy-requests/{{privacyRequestId}}/refuse",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "privacy-requests",
                "{{privacyRequestId}}",
                "refuse"
              ]
            },
            "description": "No, and why. Requires shop.privacy.manage.\n\nA refusal nobody explained is one nobody looked at, so the reason is required and is kept on the request. Same rule as a courier settlement variance and a reconciliation difference.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Identity could not be confirmed from the account alone.\"\n}"
            }
          }
        },
        {
          "name": "Privacy - Read a bundle",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/privacy-requests/{{privacyRequestId}}/download",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "privacy-requests",
                "{{privacyRequestId}}",
                "download"
              ]
            },
            "description": "What the export contains, for the person about to send it. Requires shop.privacy.manage.\n\nReading a bundle is reading one customer's whole file, so it sits behind the same permission as generating one — and it is how a request gets answered when the customer would rather be told than handed a download link. Gated on the DATE, like every other read of it."
          }
        },
        {
          "name": "Reviews - Queue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/reviews?status=pending&page=1",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "reviews"
              ],
              "query": [
                {
                  "key": "status",
                  "value": "pending"
                },
                {
                  "key": "page",
                  "value": "1"
                }
              ]
            },
            "description": "The moderation queue (storefront uplift, second pass): reviews by status with the pending count. `shop.reviews.moderate` is its own permission, apart from writing copy - a customer's words reach every other customer only after a person here has read them."
          }
        },
        {
          "name": "Reviews - Approve",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/reviews/{{reviewId}}/approve",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "reviews",
                "{{reviewId}}",
                "approve"
              ]
            },
            "description": "Put a review on the website. Bumps the catalogue cache: the stars on every card carrying the product change in the same act."
          }
        },
        {
          "name": "Reviews - Reject",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              },
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/reviews/{{reviewId}}/reject",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "reviews",
                "{{reviewId}}",
                "reject"
              ]
            },
            "description": "Keep a review off the website, with a WRITTEN reason the customer is shown on their own list. A rejection with no note is refused.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"note\": \"Names a member of staff.\"\n}"
            }
          }
        },
        {
          "name": "Content - Image Library",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenant}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/webshop/media?search=",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "webshop",
                "media"
              ],
              "query": [
                {
                  "key": "search",
                  "value": ""
                }
              ]
            },
            "description": "The image library, for choosing a listing's hero and gallery pictures (storefront uplift, second pass). The same rows the settings screen lists, reachable with the CONTENT permission rather than the settings one."
          }
        }
      ]
    },
    {
      "name": "Accounting (T085-T088) - PAID module",
      "item": [
        {
          "name": "Chart - Accounts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/accounts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "accounts"
              ]
            },
            "description": "The chart of accounts (T085, Screen 12.1). Requires accounting.view.\n\nReturns the tree, the five account TYPES (which decide an account's normal side and which statement it appears on), and `unmapped_roles` — the posting roles nothing is mapped to. That last list is the useful half: an unmapped role does not error anywhere, it quietly PARKS postings until somebody looks.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Chart - Seed the default chart",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/accounts/seed",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "accounts",
                "seed"
              ]
            },
            "description": "Seeds a conventional chart and maps the roles it covers (9.1). Requires accounting.accounts.manage.\n\nIDEMPOTENT AND NON-DESTRUCTIVE: a code that already exists is left exactly as it is. Somebody who renamed 4100 to Turnover must not have it undone by an accidental second run, and a chart with postings behind it is never rewritten by a seeder.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Chart - Create account",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/accounts",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "accounts"
              ]
            },
            "description": "Requires accounting.accounts.manage.\n\n`type` is one of asset, liability, equity, income, expense and decides the normal side and the statement. `is_postable` false makes a HEADING — postings land on leaves, because posting to a parent and its child double-counts on every roll-up. `is_control` marks an account fed by a subledger, which a manual journal may never touch.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"6150\",\n  \"name\": \"Sundries\",\n  \"type\": \"expense\",\n  \"parent_id\": null\n}"
            }
          }
        },
        {
          "name": "Chart - Update account",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/accounts/{{accountId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "accounts",
                "{{accountId}}"
              ]
            },
            "description": "Requires accounting.accounts.manage.\n\nTHE TYPE FREEZES once anything has been posted: changing it would move figures between the balance sheet and the profit and loss retrospectively, so every report drawn before the change stops reproducing and nothing says why.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Sundry expenses\"\n}"
            }
          }
        },
        {
          "name": "Chart - Remove account",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/accounts/{{accountId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "accounts",
                "{{accountId}}"
              ]
            },
            "description": "Requires accounting.accounts.manage.\n\nRefused once the account has postings — an account with history is DEACTIVATED, never removed, because every journal that named it must still resolve or the general ledger stops being readable. Also refused while it has children.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Chart - Posting roles",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/roles",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "roles"
              ]
            },
            "description": "The map the posting engine reads (D2). Requires accounting.view.\n\nThe engine names ROLES — cash, receivable, sales — and never an account, because every business's chart is different and the accountant owns it. Returns every role including the unmapped ones.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Chart - Map a role",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/roles/{{role}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "roles",
                "{{role}}"
              ]
            },
            "description": "Point a posting role at an account. Requires accounting.accounts.manage.\n\nREFUSED IF THE TYPE IS WRONG. Mapping `sales` to an expense account puts every sale on the wrong side of the profit and loss — and the trial balance still balances, so nothing downstream ever notices. Refused here, at the only moment anybody is looking. Also refused for a heading.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"account_id\": 22\n}"
            }
          }
        },
        {
          "name": "Periods - List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/periods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "periods"
              ]
            },
            "description": "Accounting periods (D6). Requires accounting.view.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Periods - Open one",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/periods",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "periods"
              ]
            },
            "description": "Requires accounting.periods.manage.\n\nREFUSES AN OVERLAP: two periods covering one day would let a posting land in either, and the trial balance would depend on which one somebody happened to look at.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"code\": \"2026-08\",\n  \"name\": \"August 2026\",\n  \"starts_on\": \"2026-08-01\",\n  \"ends_on\": \"2026-08-31\"\n}"
            }
          }
        },
        {
          "name": "Periods - Open a financial year",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/periods/year",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "periods",
                "year"
              ]
            },
            "description": "Twelve calendar months ending at the configured financial year end (9.7). Requires accounting.periods.manage. Idempotent: a month that exists is left alone whatever its status, so a second run can never reopen a period somebody locked.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ending_year\": 2027\n}"
            }
          }
        },
        {
          "name": "Periods - Close",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/periods/{{periodId}}/close",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "periods",
                "{{periodId}}",
                "close"
              ]
            },
            "description": "No new postings; corrections still allowed. Requires accounting.periods.manage.\n\nClosing says the figures are being REVIEWED, and a correction found during that review is what closing is for.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Periods - Lock",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/periods/{{periodId}}/lock",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "periods",
                "{{periodId}}",
                "lock"
              ]
            },
            "description": "Nothing more goes in, not even a reversal. Requires accounting.periods.manage.\n\nLocking says the figures have been FILED. After that a correction is a visible adjustment in the current period, never a quiet edit to a signed one — the same discipline as a prepared tax return.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role."
          }
        },
        {
          "name": "Periods - Reopen",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/periods/{{periodId}}/reopen",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "periods",
                "{{periodId}}",
                "reopen"
              ]
            },
            "description": "ITS OWN PERMISSION (accounting.periods.reopen), apart from closing.\n\nFigures somebody may already have relied on are about to change, so the reason is required and kept — it is the one act an auditor will ask about. The close and lock stamps SURVIVE the reopening: they are the record that this period was once finished with, which is what makes the reopening worth looking at.\n\nRequires the PAID `accounting` module. The module check runs BEFORE the permission check, so a tenant that has not bought accounting is told its organisation does not have the feature rather than that the caller is not allowed — the more useful answer, and one that does not vary by role.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"A supplier invoice for August arrived in September.\"\n}"
            }
          }
        },
        {
          "name": "Journals - List",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/journals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "journals"
              ]
            },
            "description": "The general ledger (T086, Screen 12.2). Requires accounting.view.\n\nEvery posting NAMES THE BUSINESS EVENT that produced it, which is what makes a figure explainable a year later when the person who posted it has left and the account mapping has since changed.\n\nRequires the PAID `accounting` module."
          }
        },
        {
          "name": "Journals - One journal",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/journals/{{journalId}}",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "journals",
                "{{journalId}}"
              ]
            },
            "description": "Requires accounting.view.\n\nEach line keeps the ROLE it was posted under as well as the account it landed in, so \"why did this land in 4100\" is answerable after the mapping changes. `subject_type` and `subject_id` are the drill-down back to the customer or supplier.\n\nRequires the PAID `accounting` module."
          }
        },
        {
          "name": "Journals - Reverse",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/journals/{{journalId}}/reverse",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "journals",
                "{{journalId}}",
                "reverse"
              ]
            },
            "description": "The only correction there is. Requires accounting.journals.reverse.\n\nA POSTED JOURNAL REFUSES AN EDIT — at model level, and the table has no `updated_at` — so the original stays as the record of what was believed at the time. The reversal is a line-for-line mirror, DATED TODAY rather than backdated into the original's period: backdating would go round the period lock, which is exactly what locking exists to prevent. Reversing twice is refused, because the second would silently re-post the original figures.\n\nRequires the PAID `accounting` module.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Rung up against the wrong customer.\"\n}"
            }
          }
        },
        {
          "name": "Parked postings - Queue",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/parked",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "parked"
              ]
            },
            "description": "Postings that could not be made (T086, D5). Requires accounting.view.\n\nTHE BUSINESS EVENT IS NEVER ROLLED BACK. The goods left the shelf and the customer paid; if the ledger cannot record it — an unmapped role, a locked period, no period at all — the document stands and the posting waits here. An accounting module that can refuse a sale is one a shop switches off, and then the ledger stops being true anyway.\n\n`by_reason` groups the queue by what each is waiting on, because forty sales blocked by one unmapped account is ONE thing to fix and a list of forty is not.\n\nRequires the PAID `accounting` module."
          }
        },
        {
          "name": "Parked postings - Retry",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/parked/{{parkedId}}/retry",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "parked",
                "{{parkedId}}",
                "retry"
              ]
            },
            "description": "Replays the STORED EVENT once the obstacle is cleared. Requires accounting.postings.resolve.\n\nThe whole event was kept rather than a summary, which is what makes this possible — the same shape as replaying a parked webhook. The class is resolved from an ALLOWLIST keyed by event name, never from a class name in the payload: the name comes off a stored row, and resolving whatever it spells would turn a database column into code execution.\n\nHarmless to repeat: if the obstacle is still there it simply parks again with the reason updated.\n\nRequires the PAID `accounting` module."
          }
        },
        {
          "name": "Parked postings - Dismiss",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/parked/{{parkedId}}/dismiss",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "parked",
                "{{parkedId}}",
                "dismiss"
              ]
            },
            "description": "Decide a posting will never be made. Requires accounting.postings.resolve.\n\nIts own act rather than a delete: \"we looked at this and chose not to post it\" and \"somebody removed a row\" are different facts, and only one of them answers an auditor. The reason is required.\n\nRequires the PAID `accounting` module.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"reason\": \"Duplicate of a sale voided on the same day.\"\n}"
            }
          }
        },
        {
          "name": "Journals - What a manual journal may use",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/journals/options",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "journals",
                "options"
              ]
            },
            "description": "The accounts a PERSON may post to (T087, 9.11). Requires accounting.journals.create.\n\nA SMALLER SET THAN THE ENGINE'S. Control accounts — receivables, payables, inventory — are fed by their subledgers and never appear here. Served by the API rather than filtered on the client, so a screen cannot drift from the rule it is showing, and the response carries a `note` saying why they are missing: somebody looking for receivables and not finding it will otherwise assume the screen is broken.\n\nRequires the PAID `accounting` module."
          }
        },
        {
          "name": "Journals - Post a manual journal",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/journals",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "journals"
              ]
            },
            "description": "ACCOUNTANT-LEVEL (T087). Requires accounting.journals.create — apart from reading the ledger and apart from reversing, because this writes figures nothing else produced and the only thing behind them is the person who typed them.\n\nTHE ENGINE AND A PERSON HAVE DIFFERENT RIGHTS. The posting engine MUST touch control accounts: receivable moves because a sale went on account, and those postings are the subledger's counterpart, kept equal by construction. A hand-written entry into the same account changes what the ledger says the customers owe without changing what the customer subledger says, and the two views then disagree in a way nobody sees until somebody reconciles — by which time it has been reported on. Refused, naming the account.\n\nEverything the engine must satisfy applies here too: it BALANCES (refused with the difference named), it lands in an OPEN period (a closed one takes corrections, not new postings), and once posted it can only be REVERSED — there is no edit endpoint and no delete endpoint, by design.\n\n`origin` is always `manual` and there is deliberately no source document: a journal claiming to come from a business event nobody can find is the one thing an auditor cannot check.\n\nRequires the PAID `accounting` module.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"entry_date\": \"2026-09-02\",\n  \"description\": \"Bank charges for August\",\n  \"lines\": [\n    {\n      \"account_id\": 31,\n      \"debit\": \"450.0000\",\n      \"description\": \"Charges\"\n    },\n    {\n      \"account_id\": 4,\n      \"credit\": \"450.0000\",\n      \"description\": \"Charges\"\n    }\n  ]\n}"
            }
          }
        },
        {
          "name": "Opening balance - Show",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/opening-balance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "opening-balance"
              ]
            },
            "description": "The opening balance and its reconciliation (T088, D9). Requires accounting.view.\n\nA DOCUMENT, NOT A MIGRATION. A business that has been trading for years cannot start its ledger at zero, and the silent alternative — a script somebody ran once whose numbers nobody can reproduce — is what E1 refused for opening stock.\n\nThe reconciliation answers BOTH halves of the claim: `balances` and `agrees_with_operations`. Balancing alone proves nothing, because a document that balances can still say the stock is worth something the layers disagree with.\n\nRequires the PAID `accounting` module."
          }
        },
        {
          "name": "Opening balance - Propose",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/opening-balance/propose",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "opening-balance",
                "propose"
              ]
            },
            "description": "Builds a draft from what the operational side already knows. Requires accounting.opening.manage.\n\nTHREE THINGS, AND ONLY THREE: what the stock is worth (the cost layers, including what is on a van), what customers owe (their subledger), what is owed to suppliers (theirs). The bank, the fixed assets and the capital are outside this system's knowledge, and proposing a figure for them would be inventing one — a person types those.\n\nProposing again REPLACES the draft's lines rather than appending: a second proposal is a re-read of the same facts, and appending would double every figure. Refused outright once an opening balance has been posted.\n\nRequires the PAID `accounting` module.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"as_at\": \"2026-04-01\",\n  \"notes\": \"Ledger starts at the beginning of the financial year.\"\n}"
            }
          }
        },
        {
          "name": "Opening balance - Save what the accountant decided",
          "request": {
            "method": "PUT",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/opening-balance",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "opening-balance"
              ]
            },
            "description": "Requires accounting.opening.manage.\n\nBOTH FIGURES ARE KEPT. What the system proposed survives the edit, beside what was decided, and the document reports the difference as an adjustment. An accountant may know the stock was written down or that a customer will never pay — a difference is not automatically wrong. It is wrong to have one nobody can see.\n\nRequires the PAID `accounting` module.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"lines\": [\n    {\n      \"account_id\": 6,\n      \"debit\": \"5500.0000\",\n      \"credit\": \"0\",\n      \"description\": \"Stock, written down\"\n    },\n    {\n      \"account_id\": 4,\n      \"debit\": \"15000.0000\",\n      \"credit\": \"0\",\n      \"description\": \"Bank\"\n    }\n  ]\n}"
            }
          }
        },
        {
          "name": "Opening balance - Post",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              },
              {
                "key": "Accept",
                "value": "application/json"
              },
              {
                "key": "Authorization",
                "value": "Bearer {{token}}"
              },
              {
                "key": "X-Tenant",
                "value": "{{tenantSlug}}"
              }
            ],
            "url": {
              "raw": "{{baseUrl}}/accounting/opening-balance/post",
              "host": [
                "{{baseUrl}}"
              ],
              "path": [
                "accounting",
                "opening-balance",
                "post"
              ]
            },
            "description": "Becomes ONE dated journal. Requires accounting.opening.manage.\n\nTHE RESIDUE GOES TO OPENING BALANCE EQUITY AS A VISIBLE LINE, never as a silent plug: a balancing figure nobody looked at is where a bad opening balance hides. The amount is shown as `residual` before posting, so it is never discovered afterwards.\n\nThe opening journal is the ONE document that may write to control accounts without a subledger movement behind it — it is the statement of what those subledgers already contain, which is the exception 9.11 is drawn around. After this the only correction is reversing the journal, like any other posting.\n\nRequires the PAID `accounting` module."
          }
        }
      ]
    }
  ]
}
