{
  "info": {
    "_postman_id": "inex-public-api-v1",
    "name": "Inex Public API v1",
    "description": "Save on Express public integration API.\n\nBase path: `/integrations/public-api/v1`\n\n**Auth:** `X-API-Key` header (`inex_api_{32 hex}`), integration type `public-api`.\n\n**Typical flow:** Rate → Ship → Labels (optional) → Track → Cancel (optional)\n\nSee `v1-api.md` for full reference.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "value",
        "value": "{{apiKey}}",
        "type": "string"
      },
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "baseUrl",
      "value": "https://api.dev.saveonexpress.ca",
      "type": "string"
    },
    {
      "key": "apiKey",
      "value": "inex_api_00000000000000000000000000000000",
      "type": "string"
    },
    {
      "key": "rateResponseId",
      "value": "",
      "type": "string"
    },
    {
      "key": "orderId",
      "value": "",
      "type": "string"
    },
    {
      "key": "labelType",
      "value": "PDF",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "Rate",
      "description": "Scope: `shipments:rate`",
      "item": [
        {
          "name": "POST Rate",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('Status 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has data.rates', () => {",
                  "  pm.expect(json.data).to.have.property('rates');",
                  "  pm.expect(json.data.rates.length).to.be.above(0);",
                  "});",
                  "const rate = json.data.rates[0];",
                  "pm.test('Rate omits internal fields', () => {",
                  "  pm.expect(rate).to.not.have.property('carrierAccount');",
                  "  pm.expect(rate).to.not.have.property('pk');",
                  "  pm.expect(rate).to.not.have.property('sk');",
                  "  pm.expect(rate.rate?.totals).to.not.have.property('discounted');",
                  "  (rate.rate?.charges || []).forEach((c) => {",
                  "    pm.expect(c).to.not.have.property('margin');",
                  "    pm.expect(c).to.not.have.property('discounted');",
                  "  });",
                  "});",
                  "if (rate?.rateResponseId) {",
                  "  pm.collectionVariables.set('rateResponseId', rate.rateResponseId);",
                  "  console.log('Saved rateResponseId:', rate.rateResponseId);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"shippingOptions\": {\n    \"shippingDate\": \"2026-05-25\"\n  },\n  \"shipper\": {\n    \"personName\": \"Jane Shipper\",\n    \"addressLine1\": \"100 Main St\",\n    \"city\": \"Montreal\",\n    \"provinceCode\": \"QC\",\n    \"postalCode\": \"H2X 1Y4\",\n    \"countryCode\": \"CA\",\n    \"phoneNumber\": \"5145550100\"\n  },\n  \"recipient\": {\n    \"personName\": \"John Recipient\",\n    \"addressLine1\": \"200 King St W\",\n    \"city\": \"Toronto\",\n    \"provinceCode\": \"ON\",\n    \"postalCode\": \"M5H 1J1\",\n    \"countryCode\": \"CA\",\n    \"phoneNumber\": \"4165550200\"\n  },\n  \"packages\": {\n    \"unitOfMeasure\": \"imperial\",\n    \"pieces\": [\n      {\n        \"quantity\": 1,\n        \"weight\": 5,\n        \"length\": 10,\n        \"width\": 8,\n        \"height\": 6\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/integrations/public-api/v1/rate",
            "description": "Create a rate request and return carrier rates synchronously.\n\nResponse `rates[]` items omit internal fields (`carrierAccount`, margins, discounted pricing, DynamoDB keys). See `v1-api.md` and `rate.json`.\n\nSaves `rateResponseId` from the first rate to collection variables for Ship."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Ship",
      "description": "Scope: `shipments:create`\n\nOrganization billing must be `account`.",
      "item": [
        {
          "name": "POST Ship",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('Status 200', () => pm.response.to.have.status(200));",
                  "pm.test('Ship success', () => {",
                  "  pm.expect(json.data.status).to.eql('success');",
                  "});",
                  "const meta = json.data?.metadata;",
                  "if (meta?.orderId) {",
                  "  pm.collectionVariables.set('orderId', meta.orderId);",
                  "  console.log('Saved orderId:', meta.orderId);",
                  "}",
                  "const firstLabel = meta?.labels?.available?.[0];",
                  "if (firstLabel?.code) {",
                  "  pm.collectionVariables.set('labelType', firstLabel.code);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "body": {
              "mode": "raw",
              "raw": "",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/integrations/public-api/v1/ship/{{rateResponseId}}",
            "description": "Create shipment from rate response. No request body.\n\nSaves `orderId` and first `labelType` from `metadata.labels` when present."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Labels",
      "description": "Scope: `shipments:labels` or `shipments:create`",
      "item": [
        {
          "name": "GET List label types",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('Status 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has available types', () => {",
                  "  pm.expect(json.data.available).to.be.an('array');",
                  "});",
                  "const first = json.data?.available?.[0];",
                  "if (first?.code) {",
                  "  pm.collectionVariables.set('labelType', first.code);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/labels/{{orderId}}",
            "description": "List available label types with public API download URLs."
          },
          "response": []
        },
        {
          "name": "GET Download label",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "pm.test('Redirect or JSON', () => {",
                  "  const code = pm.response.code;",
                  "  pm.expect([200, 302]).to.include(code);",
                  "});",
                  "if (pm.response.code === 302) {",
                  "  pm.test('Has Location header', () => {",
                  "    pm.response.to.have.header('Location');",
                  "  });",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/labels/{{orderId}}/{{labelType}}",
            "description": "Download or generate a label.\n\n**302** → signed S3 URL in `Location` (enable **Settings → Automatically follow redirects** to fetch the file).\n\n**200** → may return `available[]` if type was invalid; pick another `labelType`."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Cancel",
      "description": "Scope: `shipments:cancel`\n\nCancels a shipment with the carrier and updates order status. Organization integration user must have `delete shipment` permission.",
      "item": [
        {
          "name": "DELETE Cancel shipment",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('Status 200', () => pm.response.to.have.status(200));",
                  "pm.test('Cancel success', () => {",
                  "  pm.expect(json.data.status).to.eql('success');",
                  "});"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/shipments/{{orderId}}",
            "description": "Cancel (void) a shipment by order id.\n\nReturns the same status payload shape as internal void (`data.status`, `data.messages`).\n\n**409** if the order is already `void` or `canceled`."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Track",
      "description": "Scope: `shipments:track`",
      "item": [
        {
          "name": "GET Track order",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "pm.test('Status 200', () => pm.response.to.have.status(200));",
                  "pm.test('Has transitStatus', () => {",
                  "  pm.expect(json.data).to.have.property('transitStatus');",
                  "});"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/track/{{orderId}}",
            "description": "Refresh and return transit status for an order."
          },
          "response": []
        }
      ]
    },
    {
      "name": "Flow (run in order)",
      "description": "Run folder requests top-to-bottom after setting `baseUrl` and `apiKey`.",
      "item": [
        {
          "name": "1. Rate",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "const rate = json.data?.rates?.[0];",
                  "if (rate?.rateResponseId) {",
                  "  pm.collectionVariables.set('rateResponseId', rate.rateResponseId);",
                  "}"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"shippingOptions\": {\n    \"shippingDate\": \"2026-05-25\"\n  },\n  \"shipper\": {\n    \"personName\": \"Jane Shipper\",\n    \"addressLine1\": \"100 Main St\",\n    \"city\": \"Montreal\",\n    \"provinceCode\": \"QC\",\n    \"postalCode\": \"H2X 1Y4\",\n    \"countryCode\": \"CA\",\n    \"phoneNumber\": \"5145550100\"\n  },\n  \"recipient\": {\n    \"personName\": \"John Recipient\",\n    \"addressLine1\": \"200 King St W\",\n    \"city\": \"Toronto\",\n    \"provinceCode\": \"ON\",\n    \"postalCode\": \"M5H 1J1\",\n    \"countryCode\": \"CA\",\n    \"phoneNumber\": \"4165550200\"\n  },\n  \"packages\": {\n    \"unitOfMeasure\": \"imperial\",\n    \"pieces\": [\n      {\n        \"quantity\": 1,\n        \"weight\": 5,\n        \"length\": 10,\n        \"width\": 8,\n        \"height\": 6\n      }\n    ]\n  }\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": "{{baseUrl}}/integrations/public-api/v1/rate"
          },
          "response": []
        },
        {
          "name": "2. Ship",
          "event": [
            {
              "listen": "test",
              "script": {
                "exec": [
                  "const json = pm.response.json();",
                  "const meta = json.data?.metadata;",
                  "if (meta?.orderId) pm.collectionVariables.set('orderId', meta.orderId);",
                  "const first = meta?.labels?.available?.[0];",
                  "if (first?.code) pm.collectionVariables.set('labelType', first.code);"
                ],
                "type": "text/javascript"
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/ship/{{rateResponseId}}"
          },
          "response": []
        },
        {
          "name": "3. List labels",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/labels/{{orderId}}"
          },
          "response": []
        },
        {
          "name": "4. Download label",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/labels/{{orderId}}/{{labelType}}"
          },
          "response": []
        },
        {
          "name": "5. Track",
          "request": {
            "method": "GET",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/track/{{orderId}}"
          },
          "response": []
        },
        {
          "name": "6. Cancel",
          "request": {
            "method": "DELETE",
            "header": [],
            "url": "{{baseUrl}}/integrations/public-api/v1/shipments/{{orderId}}"
          },
          "response": []
        }
      ]
    }
  ]
}
