{
  "info": {
    "name": "CampNow – Sub-Categories & Availability",
    "description": "Sub-category CRUD (manual), super-category mapping, and availability grid.\n\n**These routes are ERP-agnostic** — they work the same regardless of whether the campsite uses CampingCare, Campalot, or manual availability configuration.\n\n**Setup:**\n1. Set `baseUrl` to your API base (default: http://localhost:5000)\n2. Set `campsiteToken` to a valid campsite JWT\n\n**Typical flow (manual campsite):**\n1. `POST /sub-categories` → create a manual category\n2. `PUT /sub-categories/map` → map it to super-categories\n3. `GET /availability` → view availability grid\n4. `PATCH /sub-categories/:id` → rename if needed\n5. `DELETE /sub-categories/:id` → remove + cascade-delete all AvailabilityRule docs\n\n**Typical flow (ERP campsite):**\n1. `GET /sub-categories` → list ERP-synced sub-categories\n2. `PUT /sub-categories/map` → map one or many to super-categories\n3. `GET /availability` → view availability grid\n\n**Guards:**\n- `PATCH` and `DELETE` return `403` if the sub-category was synced from an ERP (erpCategoryId ≠ null)\n- Duplicate name (case-insensitive) on `POST`/`PATCH` returns `409`\n- `DELETE` cascades hard-deletes all AvailabilityRule documents for that sub-category\n\n**Availability status values:**\n| Status | Occupied % | Meaning |\n|---|---|---|\n| `available` | 0–70% | Sufficient slots |\n| `limited` | 71–99% | Filling up |\n| `low` | 100% | Fully booked |\n| `not_available` | – | No data or manually closed |",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "variable": [
    { "key": "baseUrl",         "value": "http://localhost:5000",          "type": "string" },
    { "key": "campsiteToken",   "value": "<paste-your-campsite-jwt-here>", "type": "string" },
    { "key": "subCategoryId",   "value": "",                               "type": "string" }
  ],
  "item": [
    {
      "name": "Sub-Categories",
      "item": [
        {
          "name": "List Sub-Categories",
          "request": {
            "method": "GET",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/sub-categories",
              "host": ["{{baseUrl}}"],
              "path": ["api", "sub-categories"]
            },
            "description": "Returns all active sub-categories for the campsite.\n\n- For ERP campsites (CampingCare, Campalot), sub-categories are populated by the sync cron.\n- `erpCategoryId` is `null` for manually created sub-categories.\n- `superCategories` is `[]` until mapped via `PUT /map`.\n- `lastSyncedAt` is `null` for manual sub-categories."
          },
          "response": [
            {
              "name": "200 – Mixed mapped/unmapped",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"subCategory.list\",\n  \"data\": [\n    {\n      \"id\": \"64f1a2b3c4d5e6f7a8b9c0d1\",\n      \"erpCategoryId\": \"101\",\n      \"name\": \"Camping Pitch Standard\",\n      \"lastSyncedAt\": \"2026-04-27T08:00:00.000Z\",\n      \"superCategories\": [\n        {\n          \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\",\n          \"name_en\": \"Tent\",\n          \"name_de\": \"Zelt\",\n          \"icon\": \"tent-icon.svg\"\n        }\n      ]\n    },\n    {\n      \"id\": \"64f1a2b3c4d5e6f7a8b9c0d2\",\n      \"erpCategoryId\": \"102\",\n      \"name\": \"Glamping Pod\",\n      \"lastSyncedAt\": \"2026-04-27T08:00:00.000Z\",\n      \"superCategories\": []\n    },\n    {\n      \"id\": \"64f1a2b3c4d5e6f7a8b9c0d3\",\n      \"erpCategoryId\": null,\n      \"name\": \"Tent Pitch Premium\",\n      \"lastSyncedAt\": null,\n      \"superCategories\": [\n        {\n          \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\",\n          \"name_en\": \"Tent\",\n          \"name_de\": \"Zelt\",\n          \"icon\": \"tent-icon.svg\"\n        },\n        {\n          \"id\": \"64a1b2c3d4e5f6a7b8c9d0e2\",\n          \"name_en\": \"Premium\",\n          \"name_de\": \"Premium\",\n          \"icon\": \"premium-icon.svg\"\n        }\n      ]\n    }\n  ]\n}"
            },
            {
              "name": "200 – Empty",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"subCategory.list\",\n  \"data\": []\n}"
            },
            {
              "name": "401 – Unauthorized",
              "status": "Unauthorized",
              "code": 401,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"message\": \"Unauthorized\",\n  \"errors\": null,\n  \"data\": null\n}"
            }
          ]
        },
        {
          "name": "Create Manual Sub-Category",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const res = pm.response.json();",
                  "if (res.success && res.data?.id) {",
                  "  pm.collectionVariables.set('subCategoryId', res.data.id);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Content-Type",    "value": "application/json",         "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Tent Pitch\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/api/sub-categories",
              "host": ["{{baseUrl}}"],
              "path": ["api", "sub-categories"]
            },
            "description": "Creates a manually managed sub-category for the campsite.\n\n**Body:**\n- `name` (string, required) — 1–100 chars, unique per campsite (case-insensitive)\n\n**Guards:**\n- `409` if a sub-category with the same name already exists for this campsite\n\n**Note:** `erpCategoryId` is always `null` for manually created categories. The saved `id` is auto-stored in `{{subCategoryId}}` by the test script."
          },
          "response": [
            {
              "name": "201 – Created",
              "status": "Created",
              "code": 201,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": true,\n  \"message\": \"Sub-category created successfully\",\n  \"data\": {\n    \"id\": \"64f1a2b3c4d5e6f7a8b9c0d4\",\n    \"name\": \"Tent Pitch\",\n    \"erpCategoryId\": null,\n    \"superCategories\": []\n  }\n}"
            },
            {
              "name": "409 – Duplicate Name",
              "status": "Conflict",
              "code": 409,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"A sub-category with this name already exists.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "422 – Validation Error",
              "status": "Unprocessable Entity",
              "code": 422,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Validation failed\",\n  \"errors\": [\n    {\n      \"field\": \"name\",\n      \"message\": \"\\\"name\\\" is required\"\n    }\n  ]\n}"
            },
            {
              "name": "401 – Unauthorized",
              "status": "Unauthorized",
              "code": 401,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"message\": \"Unauthorized\",\n  \"errors\": null,\n  \"data\": null\n}"
            }
          ]
        },
        {
          "name": "Update Manual Sub-Category",
          "request": {
            "method": "PATCH",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Content-Type",    "value": "application/json",         "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Deluxe Tent Pitch\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/api/sub-categories/{{subCategoryId}}",
              "host": ["{{baseUrl}}"],
              "path": ["api", "sub-categories", "{{subCategoryId}}"]
            },
            "description": "Renames a manually created sub-category.\n\n**Body:**\n- `name` (string, required) — 1–100 chars, unique per campsite (case-insensitive)\n\n**Guards:**\n- `403` if the sub-category was synced from an ERP system (`erpCategoryId ≠ null`)\n- `404` if the sub-category does not exist or belongs to a different campsite\n- `409` if another sub-category already has the new name"
          },
          "response": [
            {
              "name": "200 – Updated",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": true,\n  \"message\": \"Sub-category updated successfully\",\n  \"data\": {\n    \"id\": \"64f1a2b3c4d5e6f7a8b9c0d4\",\n    \"name\": \"Deluxe Tent Pitch\",\n    \"erpCategoryId\": null\n  }\n}"
            },
            {
              "name": "403 – ERP Category (not editable)",
              "status": "Forbidden",
              "code": 403,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Only manually created categories can be edited or deleted.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "404 – Not Found",
              "status": "Not Found",
              "code": 404,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Sub-category not found.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "409 – Duplicate Name",
              "status": "Conflict",
              "code": 409,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"A sub-category with this name already exists.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "422 – Validation Error",
              "status": "Unprocessable Entity",
              "code": 422,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Validation failed\",\n  \"errors\": [\n    {\n      \"field\": \"name\",\n      \"message\": \"\\\"name\\\" is required\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Delete Manual Sub-Category",
          "request": {
            "method": "DELETE",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/sub-categories/{{subCategoryId}}",
              "host": ["{{baseUrl}}"],
              "path": ["api", "sub-categories", "{{subCategoryId}}"]
            },
            "description": "Hard-deletes a manually created sub-category.\n\n**Cascade:** All `AvailabilityRule` documents for this sub-category are also deleted in the same operation.\n\n**Guards:**\n- `403` if the sub-category was synced from an ERP system (`erpCategoryId ≠ null`)\n- `404` if the sub-category does not exist or belongs to a different campsite"
          },
          "response": [
            {
              "name": "200 – Deleted",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": true,\n  \"message\": \"Sub-category deleted successfully\",\n  \"data\": null\n}"
            },
            {
              "name": "403 – ERP Category (not deletable)",
              "status": "Forbidden",
              "code": 403,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Only manually created categories can be edited or deleted.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "404 – Not Found",
              "status": "Not Found",
              "code": 404,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Sub-category not found.\",\n  \"errors\": null,\n  \"data\": null\n}"
            }
          ]
        },
        {
          "name": "Sync Manual Categories (bulk save)",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Content-Type",    "value": "application/json",         "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"categories\": [\n    {\n      \"id\": \"64f1a2b3c4d5e6f7a8b9c0d3\",\n      \"name\": \"Tent Pitch Premium\",\n      \"superCategoryIds\": [\"64a1b2c3d4e5f6a7b8c9d0e1\"]\n    },\n    {\n      \"name\": \"Glamping Pod\",\n      \"superCategoryIds\": [\"64a1b2c3d4e5f6a7b8c9d0e1\", \"64a1b2c3d4e5f6a7b8c9d0e2\"]\n    }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/api/sub-categories/sync",
              "host": ["{{baseUrl}}"],
              "path": ["api", "sub-categories", "sync"]
            },
            "description": "Single endpoint for the 'Complete Configuration' button — submits the full current state of manual categories in one call.\n\n**How it works (diff against DB state):**\n- Item **with** `id` → update name + superCategoryIds\n- Item **without** `id` → create new manual category\n- Manual category in DB but **absent from payload** → hard delete + cascade delete all its AvailabilityRule docs\n\n**Body:**\n- `categories` (array, required) — can be empty `[]` to delete all manual categories\n  - `id` (ObjectId, optional) — omit for new categories\n  - `name` (string, required) — 1–100 chars\n  - `superCategoryIds` (ObjectId[], optional) — defaults to `[]`\n\n**Guards:**\n- `409` if two items in the payload share the same name (case-insensitive)\n- `404` if any `id` does not exist or belongs to a different campsite\n- `404` if any `superCategoryId` does not exist\n\n**Note:** Only affects manual categories (`erpCategoryId: null`). ERP-synced categories are never touched by this endpoint."
          },
          "response": [
            {
              "name": "200 – Saved (1 created, 1 updated, 1 deleted)",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": true,\n  \"message\": \"Camping categories saved successfully\",\n  \"data\": {\n    \"created\": 1,\n    \"updated\": 1,\n    \"deleted\": 1,\n    \"categories\": [\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d3\",\n        \"name\": \"Tent Pitch Premium\",\n        \"erpCategoryId\": null,\n        \"superCategories\": [\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\", \"name_en\": \"Tent\", \"name_de\": \"Zelt\", \"icon\": \"tent.svg\" }\n        ]\n      },\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d9\",\n        \"name\": \"Glamping Pod\",\n        \"erpCategoryId\": null,\n        \"superCategories\": [\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\", \"name_en\": \"Tent\",    \"name_de\": \"Zelt\",    \"icon\": \"tent.svg\" },\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e2\", \"name_en\": \"Premium\", \"name_de\": \"Premium\", \"icon\": null }\n        ]\n      }\n    ]\n  }\n}"
            },
            {
              "name": "200 – All deleted (empty payload)",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": true,\n  \"message\": \"Camping categories saved successfully\",\n  \"data\": {\n    \"created\": 0,\n    \"updated\": 0,\n    \"deleted\": 3,\n    \"categories\": []\n  }\n}"
            },
            {
              "name": "409 – Duplicate name in payload",
              "status": "Conflict",
              "code": 409,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"A sub-category with this name already exists.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "404 – ID not found",
              "status": "Not Found",
              "code": 404,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Sub-category not found.\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "422 – Validation Error",
              "status": "Unprocessable Entity",
              "code": 422,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Validation failed\",\n  \"errors\": [\n    {\n      \"field\": \"categories[0].name\",\n      \"message\": \"\\\"name\\\" is required\"\n    }\n  ]\n}"
            }
          ]
        },
        {
          "name": "Map Sub-Categories (single or bulk)",
          "request": {
            "method": "PUT",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Content-Type",    "value": "application/json",         "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"mappings\": [\n    {\n      \"subCategoryId\": \"64f1a2b3c4d5e6f7a8b9c0d1\",\n      \"superCategoryIds\": [\"64a1b2c3d4e5f6a7b8c9d0e1\", \"64a1b2c3d4e5f6a7b8c9d0e2\"]\n    },\n    {\n      \"subCategoryId\": \"64f1a2b3c4d5e6f7a8b9c0d2\",\n      \"superCategoryIds\": [\"64a1b2c3d4e5f6a7b8c9d0e1\"]\n    },\n    {\n      \"subCategoryId\": \"64f1a2b3c4d5e6f7a8b9c0d3\",\n      \"superCategoryIds\": []\n    }\n  ]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{baseUrl}}/api/sub-categories/map",
              "host": ["{{baseUrl}}"],
              "path": ["api", "sub-categories", "map"]
            },
            "description": "Maps one or many sub-categories to their super-categories in a single request.\n\n**Key behaviour:**\n- Each mapping **fully replaces** `mappedSuperCategoryIds` for that sub-category (`$set`, not `$addToSet`).\n- Pass `superCategoryIds: []` to **clear** a mapping.\n- All `superCategoryIds` across all mappings are validated in one DB query before any write.\n- All writes run in parallel (`Promise.all`).\n- `mappings` array must have at least 1 item.\n- Works for both ERP-synced and manually created sub-categories.\n\n**Scenarios:**\n\n| Intent | How |\n|---|---|\n| Map one sub-category to one super-category | `mappings` with 1 item, 1 superCategoryId |\n| Map one sub-category to multiple super-categories | `mappings` with 1 item, N superCategoryIds |\n| Map multiple sub-categories at once | `mappings` with N items |\n| Clear a sub-category mapping | `mappings` item with `superCategoryIds: []` |"
          },
          "response": [
            {
              "name": "200 – Single sub-category mapped",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"subCategory.mappingUpdated\",\n  \"data\": {\n    \"updated\": 1,\n    \"results\": [\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d1\",\n        \"mappedSuperCategoryIds\": [\n          \"64a1b2c3d4e5f6a7b8c9d0e1\"\n        ]\n      }\n    ]\n  }\n}"
            },
            {
              "name": "200 – Multiple sub-categories mapped",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"subCategory.mappingUpdated\",\n  \"data\": {\n    \"updated\": 3,\n    \"results\": [\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d1\",\n        \"mappedSuperCategoryIds\": [\n          \"64a1b2c3d4e5f6a7b8c9d0e1\",\n          \"64a1b2c3d4e5f6a7b8c9d0e2\"\n        ]\n      },\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d2\",\n        \"mappedSuperCategoryIds\": [\n          \"64a1b2c3d4e5f6a7b8c9d0e1\"\n        ]\n      },\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d3\",\n        \"mappedSuperCategoryIds\": []\n      }\n    ]\n  }\n}"
            },
            {
              "name": "404 – Sub-Category Not Found",
              "status": "Not Found",
              "code": 404,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"message\": \"Sub-category 64f1a2b3c4d5e6f7a8b9c0d9 not found\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "404 – SuperCategory Not Found",
              "status": "Not Found",
              "code": 404,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"message\": \"One or more super categories not found\",\n  \"errors\": null,\n  \"data\": null\n}"
            },
            {
              "name": "422 – Validation Error",
              "status": "Unprocessable Entity",
              "code": 422,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"success\": false,\n  \"message\": \"Validation failed\",\n  \"errors\": [\n    {\n      \"field\": \"mappings[0].subCategoryId\",\n      \"message\": \"Must be a valid MongoDB ObjectId\"\n    },\n    {\n      \"field\": \"mappings\",\n      \"message\": \"\\\"mappings\\\" must contain at least 1 items\"\n    }\n  ]\n}"
            }
          ]
        }
      ]
    },
    {
      "name": "Availability",
      "item": [
        {
          "name": "Get Availability Grid (default 7 days)",
          "request": {
            "method": "GET",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/availability",
              "host": ["{{baseUrl}}"],
              "path": ["api", "availability"]
            },
            "description": "Returns a sub-category-grouped availability grid, defaulting to today + 6 days (7 days total).\n\n**Only sub-categories with at least one entry in `mappedSuperCategoryIds` are included.**\n\nThis endpoint is ERP-agnostic — data comes from `AvailabilityRule` which is populated by:\n- ERP sync cron (CampingCare, Campalot)\n- Manual rules set by the campsite owner\n\n`occupiedPercentage` is computed on read, not stored.\n\nDates with no rule are filled as `not_available`.\n\n**Status values:**\n| Status | Occupied % | Meaning |\n|---|---|---|\n| `available` | 0–70% | Sufficient slots |\n| `limited` | 71–99% | Filling up |\n| `low` | 100% | Fully booked |\n| `not_available` | – | No data / manually closed |"
          },
          "response": [
            {
              "name": "200 – 7-Day Grid (sub-category grouped)",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"availability.grid\",\n  \"data\": {\n    \"fromDate\": \"2026-04-27\",\n    \"toDate\": \"2026-05-03\",\n    \"subCategories\": [\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d1\",\n        \"name\": \"Camping Pitch Standard\",\n        \"lastSyncedAt\": \"2026-04-27T08:00:00.000Z\",\n        \"superCategories\": [\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\", \"name_en\": \"Tent\", \"name_de\": \"Zelt\" }\n        ],\n        \"availability\": {\n          \"2026-04-27\": { \"status\": \"available\",     \"totalSlots\": 10, \"availableSlots\": 7, \"occupiedPercentage\": 30 },\n          \"2026-04-28\": { \"status\": \"limited\",       \"totalSlots\": 10, \"availableSlots\": 2, \"occupiedPercentage\": 80 },\n          \"2026-04-29\": { \"status\": \"low\",           \"totalSlots\": 10, \"availableSlots\": 0, \"occupiedPercentage\": 100 },\n          \"2026-04-30\": { \"status\": \"not_available\",  \"totalSlots\": 0,  \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-05-01\": { \"status\": \"not_available\",  \"totalSlots\": 0,  \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-05-02\": { \"status\": \"available\",     \"totalSlots\": 10, \"availableSlots\": 10, \"occupiedPercentage\": 0 },\n          \"2026-05-03\": { \"status\": \"available\",     \"totalSlots\": 10, \"availableSlots\": 9,  \"occupiedPercentage\": 10 }\n        }\n      },\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d3\",\n        \"name\": \"Glamping Pod\",\n        \"lastSyncedAt\": \"2026-04-27T08:00:00.000Z\",\n        \"superCategories\": [\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\", \"name_en\": \"Tent\",    \"name_de\": \"Zelt\" },\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e2\", \"name_en\": \"Premium\", \"name_de\": \"Premium\" }\n        ],\n        \"availability\": {\n          \"2026-04-27\": { \"status\": \"limited\",      \"totalSlots\": 4, \"availableSlots\": 1, \"occupiedPercentage\": 75 },\n          \"2026-04-28\": { \"status\": \"not_available\", \"totalSlots\": 0, \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-04-29\": { \"status\": \"not_available\", \"totalSlots\": 0, \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-04-30\": { \"status\": \"not_available\", \"totalSlots\": 0, \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-05-01\": { \"status\": \"not_available\", \"totalSlots\": 0, \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-05-02\": { \"status\": \"not_available\", \"totalSlots\": 0, \"availableSlots\": 0, \"occupiedPercentage\": 0 },\n          \"2026-05-03\": { \"status\": \"not_available\", \"totalSlots\": 0, \"availableSlots\": 0, \"occupiedPercentage\": 0 }\n        }\n      }\n    ]\n  }\n}"
            },
            {
              "name": "200 – No mapped sub-categories yet",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"availability.grid\",\n  \"data\": {\n    \"fromDate\": \"2026-04-27\",\n    \"toDate\": \"2026-05-03\",\n    \"subCategories\": []\n  }\n}"
            },
            {
              "name": "401 – Unauthorized",
              "status": "Unauthorized",
              "code": 401,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": false,\n  \"message\": \"Unauthorized\",\n  \"errors\": null,\n  \"data\": null\n}"
            }
          ]
        },
        {
          "name": "Get Availability Grid (custom date range)",
          "request": {
            "method": "GET",
            "header": [
              { "key": "Authorization",   "value": "Bearer {{campsiteToken}}", "type": "text" },
              { "key": "Accept-Language", "value": "en",                       "type": "text" }
            ],
            "url": {
              "raw": "{{baseUrl}}/api/availability?fromDate=2026-04-27&toDate=2026-05-10",
              "host": ["{{baseUrl}}"],
              "path": ["api", "availability"],
              "query": [
                { "key": "fromDate", "value": "2026-04-27", "description": "Start date (YYYY-MM-DD). Defaults to today." },
                { "key": "toDate",   "value": "2026-05-10", "description": "End date (YYYY-MM-DD). Defaults to today + 6 days." }
              ]
            },
            "description": "Same as the default endpoint but with explicit `fromDate` and `toDate` query params.\n\nAll dates in the range are returned. Dates with no rule for a sub-category are filled with `not_available`."
          },
          "response": [
            {
              "name": "200 – 14-Day Custom Range",
              "status": "OK",
              "code": 200,
              "header": [{ "key": "Content-Type", "value": "application/json" }],
              "body": "{\n  \"status\": true,\n  \"message\": \"availability.grid\",\n  \"data\": {\n    \"fromDate\": \"2026-04-27\",\n    \"toDate\": \"2026-05-10\",\n    \"subCategories\": [\n      {\n        \"id\": \"64f1a2b3c4d5e6f7a8b9c0d1\",\n        \"name\": \"Camping Pitch Standard\",\n        \"superCategories\": [\n          { \"id\": \"64a1b2c3d4e5f6a7b8c9d0e1\", \"name_en\": \"Tent\", \"name_de\": \"Zelt\" }\n        ],\n        \"availability\": {\n          \"2026-04-27\": { \"status\": \"available\",    \"totalSlots\": 10, \"availableSlots\": 7, \"occupiedPercentage\": 30 },\n          \"2026-04-28\": { \"status\": \"not_available\", \"totalSlots\": 0,  \"availableSlots\": 0, \"occupiedPercentage\": 0 }\n        }\n      }\n    ]\n  }\n}"
            }
          ]
        }
      ]
    }
  ]
}
