{
  "info": {
    "name": "Dishatrak API",
    "description": "Merchant-facing REST API: shipments, carriers, carrier accounts, delivery exceptions, and the mock carrier's inbound webhook. Every request here matches marketing/static/marketing/guides/dishatrak-api-integration-guide.pdf field-for-field -- both were built from the same source (core/permissions.py, shipments/api/*, carriers/api/*, delivery_exceptions/api/*, tracking/api/views.py), not from each other.\n\nSet `base_url` and `api_key` in this collection's Variables tab before running anything. Get an API key from the dashboard's API Keys page.\n\nInteractive docs for the same API: {{base_url}}/api/docs/ (Swagger, Try it out) and /api/redoc/. Limits: 300 requests/minute per key (HTTP 429 + Retry-After beyond). Send an Idempotency-Key header on create so retries never double-book.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      { "key": "key", "value": "Authorization", "type": "string" },
      { "key": "value", "value": "Api-Key {{api_key}}", "type": "string" },
      { "key": "in", "value": "header", "type": "string" }
    ]
  },
  "variable": [
    { "key": "base_url", "value": "http://127.0.0.1:8000", "description": "No trailing slash. Use your deployed domain in production." },
    { "key": "api_key", "value": "", "description": "From the dashboard's API Keys page. Sent as: Authorization: Api-Key <this>" },
    { "key": "shipment_id", "value": "", "description": "Auto-filled by \"Create a shipment\"'s test script -- or paste one in yourself." },
    { "key": "warehouse_id", "value": "", "description": "A saved Warehouse's id, copied from the dashboard's Warehouses page (no API to list them)." },
    { "key": "carrier_id", "value": "", "description": "A Carrier id from \"List the courier catalog\"'s response." },
    { "key": "carrier_account_id", "value": "", "description": "Auto-filled by \"Enroll a carrier account\"'s test script." },
    { "key": "exception_id", "value": "", "description": "A DeliveryException id from \"List exceptions\"'s response." },
    { "key": "mock_webhook_secret", "value": "dev-webhook-secret", "description": "Must match MOCK_CARRIER_WEBHOOK_SECRET in the server's own .env -- this is only the default from .env.example." }
  ],
  "item": [
    {
      "name": "Shipments",
      "item": [
        {
          "name": "List shipments",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/?status=in_transit&page_size=25",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", ""],
              "query": [
                { "key": "status", "value": "in_transit", "description": "Optional. Any lifecycle status.", "disabled": true },
                { "key": "awb", "value": "", "description": "Optional. Exact AWB number.", "disabled": true },
                { "key": "merchant_order_ref", "value": "", "description": "Optional. Your order reference.", "disabled": true },
                { "key": "created_after", "value": "2026-10-01", "description": "Optional. YYYY-MM-DD.", "disabled": true },
                { "key": "page_size", "value": "25", "description": "Optional. Max 100.", "disabled": true }
              ]
            },
            "description": "Paginated, newest first, 25 per page by default (page_size up to 100). Tick the query params to filter by status, AWB, order reference or creation date."
          }
        },
        {
          "name": "Create a shipment",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const body = pm.response.json();",
                  "    pm.collectionVariables.set('shipment_id', body.id);",
                  "    console.log('shipment_id set to', body.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [
              { "key": "Content-Type", "value": "application/json" },
              { "key": "Idempotency-Key", "value": "{{$guid}}", "description": "Optional. Reuse the same value when retrying a timed-out request: you get the original shipment back instead of a second booking." }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"merchant_order_ref\": \"ORDER-{{$timestamp}}\",\n  \"pickup_address\": {\n    \"contact_name\": \"Warehouse Team\",\n    \"phone\": \"9876500000\",\n    \"line1\": \"Plot 14, Sector 18\",\n    \"city\": \"Gurgaon\",\n    \"state\": \"Haryana\",\n    \"pincode\": \"122015\"\n  },\n  \"delivery_address\": {\n    \"contact_name\": \"Aditi Rao\",\n    \"phone\": \"9123456789\",\n    \"line1\": \"22 MG Road\",\n    \"city\": \"Mumbai\",\n    \"state\": \"Maharashtra\",\n    \"pincode\": \"400001\"\n  },\n  \"weight_grams\": 850,\n  \"length_cm\": \"20.00\",\n  \"width_cm\": \"15.00\",\n  \"height_cm\": \"10.00\",\n  \"declared_value\": \"1499.00\",\n  \"cod_amount\": null,\n  \"tags\": [\"urgent\", \"gift-wrap\"]\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", ""]
            },
            "description": "Creates an order and allocates + books it with a courier in one call. 400 on invalid input (weight 1-10,000,000 g; dims 0.01-1000 cm; COD <= declared value; 6-digit Indian pincodes; 2-letter country; duplicate merchant_order_ref); 422 if no connected courier can take it; 502 if the allocated carrier's own API call fails. The example uses {{$timestamp}} so it can be sent repeatedly.\n\n`pickup_address` here can be replaced with a `warehouse` id (a saved pickup location's id from the dashboard's Warehouses page) instead -- see the next request for that shape. Exactly one of the two is required, never both. `tags` is optional, a free-form list of merchant labels (e.g. for filtering the Shipments list) -- omit it or send `[]` for none."
          }
        },
        {
          "name": "Create an international shipment (with customs data)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"merchant_order_ref\": \"EXP-{{$timestamp}}\",\n  \"pickup_address\": {\n    \"contact_name\": \"Warehouse Team\",\n    \"phone\": \"9876500000\",\n    \"line1\": \"Plot 14, Sector 18\",\n    \"city\": \"Gurgaon\",\n    \"state\": \"Haryana\",\n    \"pincode\": \"122015\"\n  },\n  \"delivery_address\": {\n    \"contact_name\": \"Ann Lee\",\n    \"phone\": \"+447700900123\",\n    \"line1\": \"5 Baker Street\",\n    \"city\": \"London\",\n    \"pincode\": \"NW1 6XE\",\n    \"country\": \"GB\"\n  },\n  \"weight_grams\": 800,\n  \"length_cm\": \"25.00\",\n  \"width_cm\": \"20.00\",\n  \"height_cm\": \"5.00\",\n  \"declared_value\": \"4200.00\",\n  \"export_type\": \"csb_v\",\n  \"incoterm\": \"DDU\",\n  \"invoice_number\": \"INV-2001\",\n  \"items\": [\n    {\n      \"description\": \"Silk scarf\",\n      \"hs_code\": \"62141010\",\n      \"quantity\": 2,\n      \"unit_value\": \"2100.00\",\n      \"country_of_origin\": \"IN\"\n    }\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "shipments",
                ""
              ]
            },
            "description": "International order with what customs needs: `export_type` (`csb_iv` gifts/samples, `csb_v` commercial), `incoterm` (`DDU` recipient pays duties, `DDP` sender pays), `invoice_number`, and `items` with 4-10 digit HS codes. These feed the dashboard's printable commercial invoice. `state` is optional outside India. The response includes `quoted_cost` when the allocated courier has a rate card."
          }
        },
        {
          "name": "Create a shipment (from a saved Warehouse)",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"merchant_order_ref\": \"ORDER-{{$timestamp}}-WH\",\n  \"warehouse\": \"{{warehouse_id}}\",\n  \"delivery_address\": {\n    \"contact_name\": \"Aditi Rao\",\n    \"phone\": \"9123456789\",\n    \"line1\": \"22 MG Road\",\n    \"city\": \"Mumbai\",\n    \"state\": \"Maharashtra\",\n    \"pincode\": \"400001\"\n  },\n  \"weight_grams\": 850,\n  \"length_cm\": \"20.00\",\n  \"width_cm\": \"15.00\",\n  \"height_cm\": \"10.00\",\n  \"declared_value\": \"1499.00\",\n  \"cod_amount\": null\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", ""]
            },
            "description": "Same endpoint as \"Create a shipment\", using `warehouse` (a saved pickup location's id) instead of typing `pickup_address` inline every time. There's no API endpoint to list/create Warehouse rows themselves -- they're managed from the dashboard's Warehouses page; copy an id from there into the `warehouse_id` collection variable. 400 if the id doesn't belong to your merchant or isn't active."
          }
        },
        {
          "name": "Retrieve a shipment",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/{{shipment_id}}/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", "{{shipment_id}}", ""]
            }
          }
        },
        {
          "name": "Get tracking history",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/{{shipment_id}}/tracking/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", "{{shipment_id}}", "tracking", ""]
            },
            "description": "Every TrackingEvent for this shipment, oldest first."
          }
        },
        {
          "name": "Cancel a shipment",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/{{shipment_id}}/cancel/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", "{{shipment_id}}", "cancel", ""]
            },
            "description": "No body. 409 if the shipment has no carrier_account/AWB yet."
          }
        },
        {
          "name": "Set a manual AWB",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"awb_number\": \"MANUAL-AWB-0001\",\n  \"label_url\": \"\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/{{shipment_id}}/set-awb/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", "{{shipment_id}}", "set-awb", ""]
            },
            "description": "For a courier this deployment has no real API client for yet. 409 if the shipment already has an AWB."
          }
        },
        {
          "name": "Report a manual tracking event",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"status\": \"in_transit\",\n  \"description\": \"Left origin facility\",\n  \"location\": \"Gurgaon Hub\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/shipments/{{shipment_id}}/report-tracking/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "shipments", "{{shipment_id}}", "report-tracking", ""]
            },
            "description": "status must be one of: pending_allocation, allocated, pickup_scheduled, in_transit, out_for_delivery, delivered, return_in_transit, failed, cancelled, returned. 409 if this shipment already has automatic (webhook/poll) tracking."
          }
        }
      ]
    },
    {
      "name": "Rates",
      "item": [
        {
          "name": "Quote a parcel across your couriers",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"origin_pincode\": \"560001\",\n  \"origin_city\": \"Bengaluru\",\n  \"origin_state\": \"Karnataka\",\n  \"dest_pincode\": \"302001\",\n  \"dest_city\": \"Jaipur\",\n  \"dest_state\": \"Rajasthan\",\n  \"dest_country\": \"IN\",\n  \"weight_grams\": 1200,\n  \"length_cm\": \"30\",\n  \"width_cm\": \"20\",\n  \"height_cm\": \"15\",\n  \"declared_value\": \"1499\",\n  \"cod_amount\": \"1499\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/rates/quote/",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api",
                "v1",
                "rates",
                "quote",
                ""
              ]
            },
            "description": "Prices one parcel against every active rate card (dashboard: Rate Cards), cheapest first, with zone, actual/volumetric/chargeable weight, itemised charges (freight, fuel, COD, insurance, ODA, docket), GST lines and an RTO estimate. Couriers with a live rate API (Aramex today) are added with `source: \"live\"`. International parcels (`dest_country` other than IN) also return `destination_charges_estimate`. Pass `warehouse` (a saved warehouse id) instead of the origin_* fields to quote from a saved pickup location. Unavailable couriers come last with `available: false` and a `reason`."
          }
        }
      ]
    },
    {
      "name": "Carriers",
      "item": [
        {
          "name": "List the courier catalog",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/carriers/?country_scope=domestic",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "carriers", ""],
              "query": [
                { "key": "country_scope", "value": "domestic", "description": "domestic | international | global. Remove to list all." },
                { "key": "courier_group", "value": "delhivery", "description": "e.g. all of one courier's product variants together.", "disabled": true }
              ]
            }
          }
        },
        {
          "name": "Get one courier",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/carriers/{{carrier_id}}/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "carriers", "{{carrier_id}}", ""]
            }
          }
        }
      ]
    },
    {
      "name": "Carrier accounts",
      "item": [
        {
          "name": "List your carrier accounts",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/carrier-accounts/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "carrier-accounts", ""]
            }
          }
        },
        {
          "name": "Enroll a carrier account",
          "event": [
            {
              "listen": "test",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "if (pm.response.code === 201) {",
                  "    const body = pm.response.json();",
                  "    pm.collectionVariables.set('carrier_account_id', body.id);",
                  "    console.log('carrier_account_id set to', body.id);",
                  "}"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"carrier\": \"{{carrier_id}}\",\n  \"credentials\": {\n    \"api_token\": \"replace-with-that-carriers-real-credential-shape\"\n  },\n  \"priority\": 100\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/carrier-accounts/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "carrier-accounts", ""]
            },
            "description": "The credentials object's shape must match that carrier's own credential_schema (see \"Get one courier\") -- this example's single api_token field is illustrative, not universal. 400 with a {\"credentials\": [...]} error if it doesn't match."
          }
        },
        {
          "name": "Get one carrier account",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/carrier-accounts/{{carrier_account_id}}/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "carrier-accounts", "{{carrier_account_id}}", ""]
            }
          }
        },
        {
          "name": "Test a carrier account's connection",
          "request": {
            "method": "POST",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/carrier-accounts/{{carrier_account_id}}/test-connection/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "carrier-accounts", "{{carrier_account_id}}", "test-connection", ""]
            },
            "description": "Calls that courier's own connection-check API with the stored credentials. 502 if that call fails."
          }
        }
      ]
    },
    {
      "name": "Delivery exceptions",
      "item": [
        {
          "name": "List exceptions",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/exceptions/?status=open",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "exceptions", ""],
              "query": [
                { "key": "status", "value": "open", "description": "open | resolved" },
                { "key": "kind", "value": "ndr", "description": "ndr | npr", "disabled": true }
              ]
            }
          }
        },
        {
          "name": "Get one exception",
          "request": {
            "method": "GET",
            "header": [],
            "url": {
              "raw": "{{base_url}}/api/v1/exceptions/{{exception_id}}/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "exceptions", "{{exception_id}}", ""]
            }
          }
        },
        {
          "name": "Resolve an exception",
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"action\": \"reattempt\",\n  \"note\": \"Customer confirmed availability tomorrow morning.\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/v1/exceptions/{{exception_id}}/resolve/",
              "host": ["{{base_url}}"],
              "path": ["api", "v1", "exceptions", "{{exception_id}}", "resolve", ""]
            },
            "description": "action must be one of that exception's own available_actions (see \"Get one exception\"): NDR -> reattempt, mark_rto, cancel. NPR -> reschedule_pickup, cancel. Add an updated_address object (same shape as pickup_address/delivery_address) when reattempting to a corrected address. 409 if the action doesn't fit this exception's kind."
          }
        }
      ]
    },
    {
      "name": "Webhooks (dev/testing only)",
      "item": [
        {
          "name": "Simulate a mock carrier tracking update",
          "event": [
            {
              "listen": "prerequest",
              "script": {
                "type": "text/javascript",
                "exec": [
                  "// Mirrors carriers/clients/mock.py's verify_webhook_signature exactly:",
                  "// hmac_sha256(MOCK_CARRIER_WEBHOOK_SECRET, raw_body).hexdigest()",
                  "const secret = pm.collectionVariables.get('mock_webhook_secret');",
                  "const body = pm.request.body.raw;",
                  "const signature = CryptoJS.HmacSHA256(body, secret).toString(CryptoJS.enc.Hex);",
                  "pm.request.headers.upsert({ key: 'X-Mock-Signature', value: signature });"
                ]
              }
            }
          ],
          "request": {
            "method": "POST",
            "header": [{ "key": "Content-Type", "value": "application/json" }],
            "body": {
              "mode": "raw",
              "raw": "{\n  \"awb_number\": \"REPLACE-WITH-A-REAL-MOCK-AWB\",\n  \"status\": \"OFD\",\n  \"description\": \"Out for delivery\"\n}",
              "options": { "raw": { "language": "json" } }
            },
            "url": {
              "raw": "{{base_url}}/api/webhooks/carriers/mock/",
              "host": ["{{base_url}}"],
              "path": ["api", "webhooks", "carriers", "mock", ""]
            },
            "description": "Not part of the merchant API -- this is what a courier calls to notify Dishatrak, so it's not authenticated with your api_key at all. Useful here only to simulate the Mock carrier's own webhook locally: awb_number must belong to a real shipment already allocated to Mock. The pre-request script signs the body with mock_webhook_secret (must match the server's MOCK_CARRIER_WEBHOOK_SECRET) -- this only works against the Mock carrier; a real courier integration signs its webhooks its own way."
          }
        }
      ]
    }
  ]
}
