{
  "info": {
    "name": "MeshHub Tenant API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
    "description": "The full MeshHub quickstart flow, ready to run: mint a trial key, search, poll the task, book, pay, and list orders.\n\n1. Run 'Mint trial API key' first — it stores the key in the collection's `api_key` variable automatically (3 mints per hour per IP; reuse the key for its 7-day life).\n2. Run 'Search' — it stores `task_id` for the poll request.\n3. 'Poll task' until status is `completed`; offers are in `result.offers`.\n4. Copy an offer's ids into 'Book', then run 'Pay order' and 'List orders'.\n\nDocs: https://merchant.meshhub.ai/docs/ · Guide: https://merchant.meshhub.ai/docs/tenant-integration.md"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      { "key": "key", "value": "X-MH-Key", "type": "string" },
      { "key": "value", "value": "{{api_key}}", "type": "string" },
      { "key": "in", "value": "header", "type": "string" }
    ]
  },
  "variable": [
    { "key": "base_url", "value": "https://merchant.meshhub.ai" },
    { "key": "api_key", "value": "" },
    { "key": "task_id", "value": "" },
    { "key": "supplier_order_id", "value": "" }
  ],
  "item": [
    {
      "name": "0 · Mint trial API key",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.response.code === 201) {",
              "  pm.collectionVariables.set('api_key', pm.response.json().key);",
              "  console.log('api_key variable set — you are authenticated.');",
              "}"
            ]
          }
        }
      ],
      "request": {
        "auth": { "type": "noauth" },
        "method": "POST",
        "url": "{{base_url}}/api/v1/auth/trial-keys/",
        "description": "No auth, no body. Returns a 7-day all-scopes trial key and stores it in the `api_key` variable. Rate-limited to 3 mints/hour per IP (429 + Retry-After) — mint once and reuse. Trial calls hit LIVE suppliers."
      }
    },
    {
      "name": "1 · Health",
      "request": {
        "auth": { "type": "noauth" },
        "method": "GET",
        "url": "{{base_url}}/api/v1/health/",
        "description": "Liveness check — no auth needed."
      }
    },
    {
      "name": "2 · Search (async)",
      "event": [
        {
          "listen": "test",
          "script": {
            "type": "text/javascript",
            "exec": [
              "if (pm.response.code === 202) {",
              "  pm.collectionVariables.set('task_id', pm.response.json().task_id);",
              "  console.log('task_id stored — run \\'3 · Poll task\\'.');",
              "}"
            ]
          }
        }
      ],
      "request": {
        "method": "POST",
        "url": "{{base_url}}/api/v1/search/",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"category\": \"hair_salon\",\n  \"currency\": \"EUR\",\n  \"extra\": {\n    \"query\": \"haircut\"\n  }\n}"
        },
        "description": "Returns 202 with a task envelope and stores `task_id`. Try other categories: car_rental (city: Palma de Mallorca has demo stock), travel (origin/destination/date_from), shipping, hotel. GET /api/v1/search/schema/?category=... lists each category's fields."
      }
    },
    {
      "name": "3 · Poll task",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/tasks/{{task_id}}/",
        "description": "Poll immediately, then back off (each poll counts against your rate limit — trial is 10 req/min). When status is `completed`, search offers are in `result.offers` and `result.field_schema` lists the fields /book/ needs."
      }
    },
    {
      "name": "4 · Book (async)",
      "request": {
        "method": "POST",
        "url": "{{base_url}}/api/v1/book/",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"category\": \"hair_salon\",\n  \"supplier_id\": \"SUPPLIER-FROM-OFFER\",\n  \"offer_id\": \"OFFER-ID-FROM-SEARCH\",\n  \"end_user\": {\n    \"first_name\": \"Ada\",\n    \"last_name\": \"Lovelace\",\n    \"email\": \"ada@example.com\",\n    \"phone\": \"+37060000000\"\n  },\n  \"extra\": {\n    \"appointment_at\": \"2026-08-01T14:30:00\"\n  },\n  \"quoted_price\": \"PRICE-FROM-OFFER\"\n}"
        },
        "description": "⚠️ Trial keys book against LIVE suppliers — run this only when you mean it. Fill supplier_id / offer_id / quoted_price from a search offer and the extra fields from result.field_schema. Returns 202; poll the task — the completed result is the order, and result.supplier_order_id is what you pay with."
      }
    },
    {
      "name": "5 · Pay order",
      "request": {
        "method": "POST",
        "url": "{{base_url}}/api/v1/payments/pay-order/",
        "header": [{ "key": "Content-Type", "value": "application/json" }],
        "body": {
          "mode": "raw",
          "raw": "{\n  \"order_code\": \"{{supplier_order_id}}\"\n}"
        },
        "description": "Set the `supplier_order_id` variable from the book result first. If the response carries requires_action + checkout_url, finish payment in the browser; otherwise a saved method was charged."
      }
    },
    {
      "name": "6 · List orders",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/orders/?limit=20",
        "description": "Newest first. Filter with ?status=, ?category= (e.g. hair_salon), ?customer=<your end_user_external_id>."
      }
    },
    {
      "name": "7 · Order detail",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/orders/{{supplier_order_id}}/",
        "description": "Full current state of one order; append timeline/ for its status history."
      }
    },
    {
      "name": "8 · Search request schema",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/search/schema/?category=hair_salon",
        "description": "Live per-category search contract — which `extra` fields search accepts for the category."
      }
    },
    {
      "name": "9 · Discovery: salon cities",
      "request": {
        "method": "GET",
        "url": "{{base_url}}/api/v1/salons/cities/",
        "description": "Cities with bookable salons — use to populate a city selector before searching. Similar endpoints exist under /api/v1/car-rentals/."
      }
    }
  ]
}
