{
  "openapi": "3.1.0",
  "info": {
    "title": "CargoCharges Shipping API",
    "version": "1.1.0",
    "description": "Public REST API for CargoCharges.com — compare international courier rates, check serviceability, track shipments, and request quotes. Operated by Invented Ideas Pvt Ltd (CIN U74999DL2016PTC302870), authorized franchise partner of CourierVia.\n\nAuthentication: API key via X-API-Key header or `?api_key=` query parameter. Keys have the format `cc_live_*` or `cc_test_*`. Get a key at https://cargocharges.com/api/register/ or contact api@cargocharges.com / WhatsApp +91-9718661166.\n\nIMPORTANT: POST /book creates a quote request, not a confirmed booking. The CargoCharges team contacts the end user within business hours (10:00–19:00 IST) to confirm pickup, payment, and dispatch.",
    "contact": {
      "name": "CargoCharges API Support",
      "email": "api@cargocharges.com",
      "url": "https://cargocharges.com/api/"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://cargocharges.com/terms-and-conditions.php"
    }
  },
  "servers": [
    {
      "url": "https://cargocharges.com",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "ApiKeyQuery": []
    }
  ],
  "tags": [
    {
      "name": "Rates",
      "description": "Compare carrier rates"
    },
    {
      "name": "Serviceability",
      "description": "Check route + commodity availability"
    },
    {
      "name": "Countries",
      "description": "List supported destinations"
    },
    {
      "name": "Tracking",
      "description": "Track shipments booked through CargoCharges"
    },
    {
      "name": "Booking",
      "description": "Submit a quote request (lead) for a shipment"
    },
    {
      "name": "Meta",
      "description": "API root and metadata"
    }
  ],
  "paths": {
    "/api/v1/": {
      "get": {
        "tags": [
          "Meta"
        ],
        "operationId": "getApiRoot",
        "summary": "API root — list endpoints and pricing tiers",
        "description": "Returns API metadata: name, version, endpoint catalogue, authentication notes, rate limits, pricing tiers, and support contacts. No authentication required. Browsers (Accept: text/html with Mozilla/Chrome/Safari UA) are redirected to /api/docs/; JSON clients receive the metadata payload.",
        "security": [],
        "responses": {
          "200": {
            "description": "API metadata.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                }
              }
            }
          },
          "302": {
            "description": "Browser redirect to /api/docs/."
          }
        }
      }
    },
    "/api/v1/rates": {
      "get": {
        "tags": [
          "Rates"
        ],
        "operationId": "getRates",
        "summary": "Compare shipping rates across carriers",
        "description": "Returns per-carrier price quotes (in INR) for shipping a parcel from India to a destination country at a given weight. Carriers include DHL, FedEx, UPS, Aramex, CourierVia.com (rebranded Prompt), and self-pickup options where applicable. Use this endpoint to power rate-shopping flows; pair with /api/v1/book to submit a quote request once the user selects a carrier.\n\nPricing notes:\n- All prices are in INR and exclude GST (18%) unless stated.\n- Pickup and packing charges, when applicable, are returned as separate per-kg fields.\n- The `surcharges.remote_area_oda` block describes a CONDITIONAL surcharge that may apply post-pickup if the destination pincode is classified remote/ODA by the carrier — not part of the quoted price.\n- Doorstep pickup is available in Delhi NCR; non-NCR pickups route via inbound courier to the Delhi NCR office (1–2 day inbound transit, free for orders above carrier threshold).",
        "parameters": [
          {
            "$ref": "#/components/parameters/CountryParam"
          },
          {
            "$ref": "#/components/parameters/WeightParam"
          },
          {
            "$ref": "#/components/parameters/CommodityParam"
          },
          {
            "$ref": "#/components/parameters/PickupCityParam"
          }
        ],
        "responses": {
          "200": {
            "description": "Rate comparison results.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/RatesResult"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/countries": {
      "get": {
        "tags": [
          "Countries"
        ],
        "operationId": "listCountries",
        "summary": "List supported destination countries",
        "description": "Returns the catalogue of destination countries CargoCharges currently services (220+). Each entry contains a numeric ID, a country name (the value to pass back to /rates, /serviceability, and /book as `country`/`receiver.country`), and an ISO 3166-1 alpha-2 country code when known. Use the `search` filter to narrow the list before presenting to a user.",
        "parameters": [
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring filter applied to country name. Examples: 'united' (matches United States, United Kingdom, United Arab Emirates), 'aus' (matches Australia, Austria).",
            "schema": {
              "type": "string",
              "maxLength": 64
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Country list.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "total": {
                              "type": "integer"
                            },
                            "countries": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/Country"
                              }
                            }
                          },
                          "required": [
                            "total",
                            "countries"
                          ]
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/serviceability": {
      "get": {
        "tags": [
          "Serviceability"
        ],
        "operationId": "checkServiceability",
        "summary": "Check route + commodity serviceability",
        "description": "Reports whether the requested destination country is serviceable, lists the carriers that operate the route, and flags commodity-level restrictions. Call this before /rates when the user is unsure if their item type (e.g. medicine, food, jewelry) is allowed — restricted commodities will short-circuit to `serviceable:false` with a reason.\n\nReturns:\n- `serviceable:true` with a non-empty `carriers[]` when at least one carrier supports the route.\n- `serviceable:false` with `reason:'not_found'` when the country is not in the catalogue.\n- `serviceable:false` with `reason:'blocked_commodity'` when the commodity is on the prohibited list (e.g. liquids, batteries by some carriers).",
        "parameters": [
          {
            "$ref": "#/components/parameters/CountryParam"
          },
          {
            "name": "weight",
            "in": "query",
            "required": false,
            "description": "Weight in kilograms (decimal). Defaults to 5 if omitted. Used to filter carriers that don't quote at that slab.",
            "schema": {
              "type": "number",
              "format": "float",
              "minimum": 0.5,
              "maximum": 300,
              "default": 5
            }
          },
          {
            "$ref": "#/components/parameters/CommodityParam"
          },
          {
            "name": "pincode",
            "in": "query",
            "required": false,
            "description": "Indian pickup pincode (6 digits). When provided, response includes `pickup_info` with `is_delhi_ncr` flag — useful to set expectation that non-NCR origins ship inbound to Delhi first.",
            "schema": {
              "type": "string",
              "pattern": "^[1-9][0-9]{5}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Serviceability result.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ServiceabilityResult"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/track": {
      "get": {
        "tags": [
          "Tracking"
        ],
        "operationId": "trackShipment",
        "summary": "Track a shipment by AWB",
        "description": "Returns the current status of a shipment booked through CargoCharges. Status normalises into one of: `processing`, `booked`, `in_transit`, `on_hold`, `delivered`, `returned`. Where the carrier flags the destination as a Remote Area / ODA, `is_remote_area:true` and `oda_surcharge` describes the conditional ₹3,500 + 18% GST charge (₹4,130 total) that will be applied post-pickup.\n\nLIMITATION: CargoCharges can only track AWBs of shipments booked through us. External AWBs (DHL/FedEx/UPS tracking numbers from other agents) are NOT in our database and will return HTTP 404 with error code `NOT_FOUND`. For those, follow `carrier_tracking_url` in the response — but only when the AWB is in our DB.",
        "parameters": [
          {
            "name": "awb",
            "in": "query",
            "required": true,
            "description": "Air Waybill (tracking number), alphanumeric. Non-alphanumeric characters are stripped server-side. CargoCharges can only track AWBs booked through us — external carrier AWBs return NOT_FOUND (HTTP 404).",
            "schema": {
              "type": "string",
              "minLength": 4,
              "maxLength": 40,
              "pattern": "^[A-Za-z0-9 -]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Shipment found.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/TrackResult"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "503": {
            "$ref": "#/components/responses/ServiceUnavailable"
          }
        }
      }
    },
    "/api/v1/book": {
      "post": {
        "tags": [
          "Booking"
        ],
        "operationId": "createQuoteRequest",
        "summary": "Submit a quote request (creates a lead, not a confirmed booking)",
        "description": "Creates a quote request, not a confirmed booking. The CargoCharges team contacts the end user within business hours (10:00–19:00 IST) to confirm pickup, payment, and dispatch. No payment is collected via this API. Response returns a `booking_id` for reference but no payment_link, no AWB, and no immediate tracking.\n\nThe submission is written into CargoCharges' CRM as a lead (`tbl_calllerremark`) and into `api_bookings` for callback tracking. The `data.status` field returns the literal string `confirmed` to indicate the lead was successfully captured — this is NOT a dispatch confirmation. Final cost may vary based on actual or volumetric weight at pickup. To poll status, use GET /api/v1/track with the AWB once the team assigns one (after pickup).\n\n**v1.1.0 (B3 Step 2):** Agent-scope keys (cc_agent_*) must supply `Idempotency-Key` and `X-End-User-Phone` headers. The end-user phone is delivered plaintext to the CargoCharges CRM and hashed in `api_bookings`. Replaying the same `Idempotency-Key` within 24 hours returns the original booking with HTTP 200 + `X-Idempotent-Replay: true` and `meta.idempotent_replay: true`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteRequest"
              },
              "example": {
                "sender": {
                  "name": "John Doe",
                  "phone": "9876543210",
                  "email": "john@example.com",
                  "address": "123 Main St",
                  "city": "New Delhi",
                  "pincode": "110001"
                },
                "receiver": {
                  "name": "Jane Smith",
                  "phone": "+14155551234",
                  "address": "456 Oak Ave, Apt 2",
                  "city": "New York",
                  "state": "NY",
                  "country": "USA",
                  "zipcode": "10001"
                },
                "shipment": {
                  "weight": 5,
                  "commodity": "clothes",
                  "carrier": "DHL",
                  "description": "Personal clothing items",
                  "declared_value": 5000,
                  "pieces": 1
                },
                "reference_id": "SHOP-ORD-12345"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Quote request captured.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/QuoteResponse"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Validation error. New in v1.1.0: agent-scope-only error codes — `IDEMPOTENCY_KEY_REQUIRED` (missing Idempotency-Key header on agent key), `AGENT_KEY_REQUIRES_END_USER_PHONE` (missing X-End-User-Phone header on agent key), `INVALID_END_USER_PHONE` (X-End-User-Phone not Indian 10-digit or E.164). See `error.code` for the specific code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "examples": {
                  "IDEMPOTENCY_KEY_REQUIRED": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "IDEMPOTENCY_KEY_REQUIRED",
                        "message": "Idempotency-Key header is required for agent-scope API keys."
                      },
                      "meta": {
                        "api_version": "1.1.0",
                        "timestamp": "2026-05-16T18:00:00+05:30",
                        "request_id": "cc_..."
                      }
                    }
                  },
                  "AGENT_KEY_REQUIRES_END_USER_PHONE": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "AGENT_KEY_REQUIRES_END_USER_PHONE",
                        "message": "X-End-User-Phone header is required for agent-scope API keys."
                      },
                      "meta": {
                        "api_version": "1.1.0",
                        "timestamp": "2026-05-16T18:00:00+05:30",
                        "request_id": "cc_..."
                      }
                    }
                  },
                  "INVALID_END_USER_PHONE": {
                    "value": {
                      "success": false,
                      "error": {
                        "code": "INVALID_END_USER_PHONE",
                        "message": "Invalid end-user phone format. Use 10-digit Indian (e.g., 9876543210) or E.164 international (e.g., +14155551234)."
                      },
                      "meta": {
                        "api_version": "1.1.0",
                        "timestamp": "2026-05-16T18:00:00+05:30",
                        "request_id": "cc_..."
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "405": {
            "$ref": "#/components/responses/MethodNotAllowed"
          },
          "422": {
            "$ref": "#/components/responses/UnprocessableEntity"
          },
          "429": {
            "$ref": "#/components/responses/RateLimitExceeded"
          },
          "500": {
            "$ref": "#/components/responses/ServerError"
          },
          "503": {
            "description": "Booking creation failed at the database layer (api_bookings or tbl_calllerremark INSERT). Retry with the same Idempotency-Key — the request is safe to repeat. If the failure occurred after api_bookings was inserted, a compensating delete is performed to keep the two tables consistent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                },
                "example": {
                  "success": false,
                  "error": {
                    "code": "BOOKING_FAILED",
                    "message": "Booking creation failed (CRM insert; rolled back). Please retry with same Idempotency-Key."
                  },
                  "meta": {
                    "api_version": "1.1.0",
                    "timestamp": "2026-05-16T18:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              }
            }
          },
          "200": {
            "description": "Idempotency replay: the same Idempotency-Key was used within 24 hours, so the original quote is returned without creating a new lead. The body is identical to the original 201 response, with `meta.idempotent_replay: true`. Also see `X-Idempotent-Replay: true` response header.",
            "headers": {
              "X-Idempotent-Replay": {
                "description": "Present and set to `true` for replayed responses.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/SuccessEnvelope"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/QuoteResponse"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "success": true,
                  "message": "Booking already exists (idempotency replay).",
                  "data": {
                    "booking_id": "CC-API-20260516-A1B2C3",
                    "reference_id": "SHOP-ORD-12345",
                    "status": "confirmed",
                    "status_display": "Booking Confirmed - Pending Pickup"
                  },
                  "meta": {
                    "api_version": "1.1.0",
                    "timestamp": "2026-05-16T18:00:00+05:30",
                    "request_id": "cc_...",
                    "idempotent_replay": true
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Optional for developer-scope keys; REQUIRED for agent-scope keys (cc_agent_live_* / cc_agent_test_*). Caller-generated identifier (e.g. UUIDv4) unique per logical booking attempt. Repeating the same value within 24 hours returns the SAME booking_id with HTTP 200 + `X-Idempotent-Replay: true` and `meta.idempotent_replay: true` — no duplicate CRM row is created. Uniqueness scope is (api_key_id, idempotency_key).",
            "schema": {
              "type": "string",
              "maxLength": 64,
              "example": "b1e7a9d3-2f4c-4b1e-9a0d-3e3c7e2f1b8a"
            }
          },
          {
            "name": "X-End-User-Phone",
            "in": "header",
            "required": false,
            "description": "REQUIRED for agent-scope keys (cc_agent_*); ignored for developer-scope. Indian 10-digit format (e.g. `9876543210`) is automatically prefixed with `+91` before hashing; OR E.164 international format (e.g. `+14155551234`). No spaces, dashes, or alternative formats. The plaintext value is delivered to the CargoCharges CRM (`tbl_calllerremark.cid`) so the team can contact the end user; a SHA-256 hash of the normalized value is stored in `api_bookings.end_user_phone_hash` for de-duplication and audit. Bookings made from `9876543210` and `+919876543210` hash to the same value.",
            "schema": {
              "type": "string",
              "oneOf": [
                {
                  "pattern": "^[6-9]\\d{9}$",
                  "description": "Indian 10-digit"
                },
                {
                  "pattern": "^\\+[1-9]\\d{1,14}$",
                  "description": "E.164 international"
                }
              ],
              "example": "9876543210"
            }
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key in the format cc_live_<32 hex> or cc_test_<32 hex>. Get one at https://cargocharges.com/api/register/."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "API key as query parameter — supported as a fallback when the X-API-Key header cannot be set (e.g., browser GET, embedded iframe). Header is preferred."
      }
    },
    "parameters": {
      "CountryParam": {
        "name": "country",
        "in": "query",
        "required": true,
        "description": "Destination country NAME (not ISO code). Examples: 'USA', 'UK', 'Canada', 'Australia', 'UAE', 'Singapore'. Call GET /api/v1/countries to list all 220+ supported names. Country codes (e.g. 'US') are returned by /countries for reference but are NOT accepted as input — pass the country_name string.",
        "schema": {
          "type": "string",
          "minLength": 2,
          "maxLength": 60
        }
      },
      "WeightParam": {
        "name": "weight",
        "in": "query",
        "required": true,
        "description": "Package weight in kilograms (decimal). Server clamps values below 0.5 up to 0.5; values above 300 are rejected. Chargeable weight is max(actual, volumetric=(LxWxH cm)/5000). For >300kg, contact sales directly.",
        "schema": {
          "type": "number",
          "format": "float",
          "minimum": 0.5,
          "maximum": 300,
          "example": 5
        }
      },
      "CommodityParam": {
        "name": "commodity",
        "in": "query",
        "required": false,
        "description": "Item type. Common values: 'documents', 'clothes', 'electronics', 'medicine', 'food', 'jewelry', 'handicraft', 'books', 'auto_parts', 'cosmetics', 'others'. Some commodities are restricted per carrier — call /api/v1/serviceability with the same `commodity` to confirm before quoting. Restricted items (per recent policy): liquids, cosmetics, supplements, pickles, milk powder, baby food.",
        "schema": {
          "type": "string",
          "maxLength": 50
        }
      },
      "PickupCityParam": {
        "name": "pickup_city",
        "in": "query",
        "required": false,
        "description": "Indian pickup city. Defaults to 'Delhi'. Delhi NCR cities supported with same-day pickup: Delhi, Gurgaon (Gurugram), Noida, Ghaziabad, Faridabad. Non-NCR cities supported with 1–2 day inbound transit to Delhi NCR office via DTDC/Delhivery/Bluedart. If pickup is not at doorstep, response will include a `notes` string explaining the inbound routing.",
        "schema": {
          "type": "string",
          "default": "Delhi",
          "maxLength": 60
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Missing or invalid input.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "missingCountry": {
                "summary": "Missing required country param",
                "value": {
                  "success": false,
                  "error": {
                    "code": "MISSING_COUNTRY",
                    "message": "Country is required."
                  },
                  "meta": {
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-14T10:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              },
              "invalidWeight": {
                "summary": "Weight out of allowed range",
                "value": {
                  "success": false,
                  "error": {
                    "code": "INVALID_WEIGHT",
                    "message": "Weight must be between 0.5 and 300 KG."
                  },
                  "meta": {
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-14T10:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              }
            }
          }
        }
      },
      "Unauthorized": {
        "description": "API key missing, malformed, inactive, or failing free-tier backlink requirement.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "missingKey": {
                "summary": "No API key provided",
                "value": {
                  "success": false,
                  "error": {
                    "code": "MISSING_API_KEY",
                    "message": "API key is required. Pass it via X-API-Key header or api_key parameter."
                  },
                  "meta": {
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-14T10:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              },
              "backlinkRequired": {
                "summary": "Free tier past 72h grace, no verified backlink",
                "value": {
                  "success": false,
                  "error": {
                    "code": "BACKLINK_REQUIRED",
                    "message": "Free tier requires a 'Powered by CargoCharges.com' dofollow backlink on your website. ...",
                    "details": {
                      "backlink_url": "https://yoursite.com",
                      "backlink_verified": false,
                      "grace_period_hours": 72,
                      "verify_endpoint": "/api/v1/backlink-verify.php?api_key=YOUR_KEY",
                      "upgrade_url": "https://cargocharges.com/api/register/"
                    }
                  },
                  "meta": {
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-14T10:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              }
            }
          }
        }
      },
      "NotFound": {
        "description": "No matching resource (e.g., AWB not in CargoCharges database, no rates for the requested route).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "examples": {
              "awbNotFound": {
                "summary": "AWB not booked through CargoCharges",
                "value": {
                  "success": false,
                  "error": {
                    "code": "NOT_FOUND",
                    "message": "Shipment not found for AWB: 1234567890"
                  },
                  "meta": {
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-14T10:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              },
              "noRates": {
                "summary": "No carriers quote this route",
                "value": {
                  "success": false,
                  "error": {
                    "code": "NO_RATES",
                    "message": "No carriers available for this route."
                  },
                  "meta": {
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-14T10:00:00+05:30",
                    "request_id": "cc_..."
                  }
                }
              }
            }
          }
        }
      },
      "MethodNotAllowed": {
        "description": "Wrong HTTP method for the endpoint (e.g., GET against /book).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "success": false,
              "error": {
                "code": "METHOD_NOT_ALLOWED",
                "message": "This endpoint only accepts POST requests."
              },
              "meta": {
                "api_version": "1.0.0",
                "timestamp": "2026-05-14T10:00:00+05:30",
                "request_id": "cc_..."
              }
            }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Input valid syntactically but business rules blocked (commodity prohibited, etc.).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "success": false,
              "error": {
                "code": "BLOCKED_COMMODITY",
                "message": "Liquids are not accepted for international shipping by any carrier we work with."
              },
              "meta": {
                "api_version": "1.0.0",
                "timestamp": "2026-05-14T10:00:00+05:30",
                "request_id": "cc_..."
              }
            }
          }
        }
      },
      "RateLimitExceeded": {
        "description": "Tier/scope rate limit exceeded (developer free=60/min, basic=120/min, pro=300/min, enterprise=1000/min; agent free=30/min, basic=60/min, pro=150/min, enterprise=500/min). Retry-After header in seconds. Enforcement is DB-backed sliding 60s window; fails OPEN on DB outage.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "success": false,
              "error": {
                "code": "RATE_LIMIT_EXCEEDED",
                "message": "Rate limit exceeded. Max 60 requests per minute for 'agent/basic'.",
                "details": {
                  "retry_after_seconds": 60,
                  "limit": 60,
                  "scope": "agent",
                  "tier": "basic"
                }
              },
              "meta": {
                "api_version": "1.1.0",
                "timestamp": "2026-05-16T17:00:00+05:30",
                "request_id": "cc_..."
              }
            }
          }
        },
        "headers": {
          "Retry-After": {
            "description": "Seconds to wait before retrying.",
            "schema": {
              "type": "integer",
              "example": 60
            }
          }
        }
      },
      "ServerError": {
        "description": "Unexpected internal error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Database unreachable or service temporarily unavailable.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            },
            "example": {
              "success": false,
              "error": {
                "code": "DB_ERROR",
                "message": "Service temporarily unavailable."
              },
              "meta": {
                "api_version": "1.0.0",
                "timestamp": "2026-05-14T10:00:00+05:30",
                "request_id": "cc_..."
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "description": "Common envelope metadata returned on every response.",
        "properties": {
          "api_version": {
            "type": "string",
            "example": "1.0.0"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "description": "RFC 3339 timestamp in Asia/Kolkata."
          },
          "request_id": {
            "type": "string",
            "description": "Opaque unique identifier for the request (cc_<uniqid>...)."
          },
          "idempotent_replay": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "Idempotency state for POST /book responses only. `null` = idempotency not engaged (developer scope, no Idempotency-Key supplied). `false` = first call with Idempotency-Key (new booking written). `true` = replayed response for a prior identical Idempotency-Key (same `booking_id`).",
            "example": false
          }
        },
        "required": [
          "api_version",
          "timestamp",
          "request_id"
        ]
      },
      "Attribution": {
        "type": "object",
        "description": "Free-tier attribution block — only present when the calling API key is on the 'free' tier. Caller MUST render the link on their site as a dofollow link or upgrade to Pro.",
        "properties": {
          "required": {
            "type": "boolean",
            "example": true
          },
          "text": {
            "type": "string",
            "example": "Powered by CargoCharges.com"
          },
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://cargocharges.com"
          },
          "html": {
            "type": "string",
            "example": "<a href=\"https://cargocharges.com\" target=\"_blank\" rel=\"dofollow\">Powered by CargoCharges.com</a>"
          },
          "notice": {
            "type": "string"
          }
        }
      },
      "SuccessEnvelope": {
        "type": "object",
        "description": "Standard success response envelope.",
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "message": {
            "type": "string"
          },
          "data": {
            "type": "object",
            "description": "Endpoint-specific payload."
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "attribution": {
            "$ref": "#/components/schemas/Attribution"
          }
        },
        "required": [
          "success",
          "message",
          "data",
          "meta"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "Standard error response envelope.",
        "properties": {
          "success": {
            "type": "boolean",
            "enum": [
              false
            ]
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "$ref": "#/components/schemas/ErrorCode"
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Endpoint/error-specific details (e.g., missing_fields, retry_after, backlink_verified). Optional.",
                "additionalProperties": true
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "meta": {
            "$ref": "#/components/schemas/Meta"
          }
        },
        "required": [
          "success",
          "error",
          "meta"
        ]
      },
      "ErrorCode": {
        "type": "string",
        "description": "All error codes emitted by the live API as of v1.0.0.",
        "enum": [
          "MISSING_API_KEY",
          "INVALID_KEY_FORMAT",
          "INVALID_API_KEY",
          "AUTH_ERROR",
          "BACKLINK_REQUIRED",
          "RATE_LIMIT_EXCEEDED",
          "MISSING_COUNTRY",
          "INVALID_COUNTRY",
          "INVALID_WEIGHT",
          "MISSING_WEIGHT",
          "MISSING_AWB",
          "MISSING_SENDER_INFO",
          "MISSING_RECEIVER_INFO",
          "MISSING_FIELDS",
          "INVALID_JSON",
          "BLOCKED_COMMODITY",
          "NO_RATES",
          "NOT_FOUND",
          "METHOD_NOT_ALLOWED",
          "BOOKING_FAILED",
          "QUERY_ERROR",
          "DB_ERROR",
          "ERROR",
          "IDEMPOTENCY_KEY_REQUIRED",
          "AGENT_KEY_REQUIRES_END_USER_PHONE",
          "INVALID_END_USER_PHONE"
        ]
      },
      "Country": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Internal numeric ID (sub_category.id)."
          },
          "country_name": {
            "type": "string",
            "example": "USA",
            "description": "Name string to pass back as `country` to other endpoints."
          },
          "country_code": {
            "type": "string",
            "description": "ISO 3166-1 alpha-2 code (e.g. 'US'). May be empty for some entries.",
            "example": "US"
          }
        },
        "required": [
          "id",
          "country_name",
          "country_code"
        ]
      },
      "RateOffer": {
        "type": "object",
        "description": "Single carrier quote.",
        "properties": {
          "carrier": {
            "type": "string",
            "example": "DHL",
            "description": "Carrier brand (DHL, FedEx, UPS, Aramex, CourierVia.com, Self). 'Prompt' is rebranded to 'CourierVia.com'."
          },
          "total_price": {
            "type": "number",
            "format": "float",
            "description": "Total quoted price for the parcel (INR, exclusive of GST and conditional ODA surcharge)."
          },
          "price_per_kg": {
            "type": "number",
            "format": "float",
            "description": "Per-kg rate (INR)."
          },
          "currency": {
            "type": "string",
            "enum": [
              "INR"
            ]
          },
          "estimated_delivery": {
            "type": "string",
            "example": "5-7 days",
            "description": "Carrier-quoted transit time, free-form."
          },
          "is_per_kg_rate": {
            "type": "boolean",
            "description": "True if total_price was computed by multiplying price_per_kg by weight; false if slab-priced."
          },
          "includes_pickup": {
            "type": "boolean",
            "description": "True when the carrier rate already includes Delhi NCR doorstep pickup."
          },
          "pickup_charge_per_kg": {
            "type": "number",
            "format": "float",
            "description": "Extra per-kg pickup charge (INR), or 0 if pickup is included."
          },
          "packing_charge_per_kg": {
            "type": "number",
            "format": "float",
            "description": "Optional packing charge per kg (INR)."
          }
        },
        "required": [
          "carrier",
          "total_price",
          "currency"
        ]
      },
      "Surcharges": {
        "type": "object",
        "description": "Conditional surcharges that may apply post-booking.",
        "properties": {
          "remote_area_oda": {
            "type": "object",
            "properties": {
              "applies": {
                "type": "string",
                "enum": [
                  "conditional"
                ],
                "description": "Always 'conditional' — applied only if carrier flags destination pincode as remote/ODA after pickup."
              },
              "amount": {
                "type": "number",
                "example": 3500,
                "description": "Base amount in INR (before GST)."
              },
              "gst_percent": {
                "type": "number",
                "example": 18
              },
              "total_with_gst": {
                "type": "number",
                "example": 4130
              },
              "currency": {
                "type": "string",
                "enum": [
                  "INR"
                ]
              },
              "description": {
                "type": "string"
              },
              "note": {
                "type": "string"
              }
            }
          }
        }
      },
      "RatesResult": {
        "type": "object",
        "properties": {
          "country": {
            "type": "string"
          },
          "weight": {
            "type": "number",
            "format": "float"
          },
          "weight_unit": {
            "type": "string",
            "enum": [
              "kg"
            ]
          },
          "commodity": {
            "type": "string",
            "description": "Echoes input or 'general' if omitted."
          },
          "pickup_city": {
            "type": "string"
          },
          "is_delhi_ncr": {
            "type": "boolean"
          },
          "carrier_count": {
            "type": "integer"
          },
          "carriers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RateOffer"
            }
          },
          "premium_charge": {
            "type": "number",
            "format": "float",
            "description": "Premium delivery surcharge (INR), 0 if not applicable."
          },
          "surcharges": {
            "$ref": "#/components/schemas/Surcharges"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ],
            "description": "Free-form notes (e.g. 'Doorstep pickup not available for this location...')."
          }
        },
        "required": [
          "country",
          "weight",
          "carriers"
        ]
      },
      "ServiceabilityResult": {
        "type": "object",
        "properties": {
          "serviceable": {
            "type": "boolean"
          },
          "country": {
            "type": "string"
          },
          "country_input": {
            "type": "string",
            "description": "Echoes the raw input when country wasn't found."
          },
          "country_code": {
            "type": "string"
          },
          "commodity": {
            "type": "string"
          },
          "commodity_restrictions": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Carrier-specific restriction codes when commodity is partially blocked. null when unrestricted."
          },
          "reason": {
            "type": "string",
            "enum": [
              "not_found",
              "blocked_commodity"
            ],
            "description": "Present only when serviceable:false."
          },
          "message": {
            "type": "string"
          },
          "pickup_info": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "is_delhi_ncr": {
                "type": "boolean"
              },
              "pincode": {
                "type": "string"
              }
            }
          },
          "carrier_count": {
            "type": "integer"
          },
          "carriers": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "carrier": {
                  "type": "string"
                },
                "estimated_delivery": {
                  "type": "string"
                },
                "has_rate": {
                  "type": "boolean"
                }
              }
            }
          }
        },
        "required": [
          "serviceable",
          "carriers"
        ]
      },
      "TrackEvent": {
        "type": "object",
        "description": "Single tracking event line (currently surfaced via carrier_tracking_url; structured event feed planned for B3).",
        "properties": {
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "location": {
            "type": "string"
          }
        }
      },
      "TrackResult": {
        "type": "object",
        "properties": {
          "awb": {
            "type": "string"
          },
          "booking_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "booked",
              "in_transit",
              "on_hold",
              "delivered",
              "returned"
            ],
            "description": "Normalised status. Mapped from flag columns in track_parcel."
          },
          "status_display": {
            "type": "string",
            "description": "Human-readable status (e.g. 'On Hold - Custom Duty')."
          },
          "carrier": {
            "type": "string"
          },
          "destination_country": {
            "type": "string"
          },
          "destination_city": {
            "type": "string"
          },
          "weight_kg": {
            "type": "number",
            "format": "float"
          },
          "booking_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "is_remote_area": {
            "type": "boolean",
            "description": "True if any tracking log line matched ODA/remote-area keywords."
          },
          "oda_surcharge": {
            "type": [
              "object",
              "null"
            ],
            "description": "Present only when is_remote_area is true.",
            "properties": {
              "type": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "gst_percent": {
                "type": "number"
              },
              "gst_amount": {
                "type": "number"
              },
              "total_with_gst": {
                "type": "number"
              },
              "currency": {
                "type": "string",
                "enum": [
                  "INR"
                ]
              },
              "reason": {
                "type": "string"
              }
            }
          },
          "carrier_tracking_url": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "description": "Deep-link to the carrier's own tracking page for this AWB."
          }
        },
        "required": [
          "awb",
          "status",
          "status_display"
        ]
      },
      "Sender": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "Sender full name."
          },
          "phone": {
            "type": "string",
            "description": "Sender Indian phone number (digits and +). Required."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Sender email."
          },
          "address": {
            "type": "string",
            "description": "Sender pickup street address."
          },
          "city": {
            "type": "string",
            "description": "Indian pickup city. Defaults to 'Delhi' if omitted."
          },
          "pincode": {
            "type": "string",
            "pattern": "^[1-9][0-9]{5}$",
            "description": "Indian 6-digit pincode."
          }
        },
        "required": [
          "name",
          "phone"
        ]
      },
      "Receiver": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "Receiver phone with country code."
          },
          "address": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "state": {
            "type": "string"
          },
          "country": {
            "type": "string",
            "description": "Destination country NAME (e.g. 'USA') — see /api/v1/countries."
          },
          "zipcode": {
            "type": "string"
          }
        },
        "required": [
          "name",
          "country"
        ]
      },
      "ShipmentInput": {
        "type": "object",
        "properties": {
          "weight": {
            "type": "number",
            "format": "float",
            "minimum": 0.5,
            "maximum": 300,
            "description": "Weight in KG."
          },
          "commodity": {
            "type": "string",
            "description": "Item type. See /serviceability for restricted items."
          },
          "carrier": {
            "type": "string",
            "description": "Preferred carrier (e.g. 'DHL'). If carrier doesn't quote this route or carrier omitted, cheapest available is used."
          },
          "description": {
            "type": "string",
            "description": "Free-form description of contents (for customs labelling)."
          },
          "declared_value": {
            "type": "number",
            "format": "float",
            "description": "Declared value in INR (for insurance/customs)."
          },
          "pieces": {
            "type": "integer",
            "minimum": 1,
            "default": 1,
            "description": "Number of parcels in this shipment."
          }
        },
        "required": [
          "weight"
        ]
      },
      "QuoteRequest": {
        "type": "object",
        "properties": {
          "sender": {
            "$ref": "#/components/schemas/Sender"
          },
          "receiver": {
            "$ref": "#/components/schemas/Receiver"
          },
          "shipment": {
            "$ref": "#/components/schemas/ShipmentInput"
          },
          "reference_id": {
            "type": "string",
            "description": "Caller's own reference ID (e.g. their internal order ID). Echoed back in response and stored for the CargoCharges team."
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "description": "Optional override of the webhook URL configured on the API key. If provided, CargoCharges will POST status updates to this URL."
          }
        },
        "required": [
          "sender",
          "receiver",
          "shipment"
        ]
      },
      "QuoteResponse": {
        "type": "object",
        "description": "Captured-lead response. Note: `status:'confirmed'` indicates the lead was successfully written to CRM — it is NOT a dispatch confirmation. The CargoCharges team contacts the sender during business hours to confirm pickup, payment, and dispatch. No AWB is returned at this stage.",
        "properties": {
          "booking_id": {
            "type": "string",
            "example": "CC-API-20260514-A1B2C3",
            "description": "CargoCharges-generated lead ID (CC-API-YYYYMMDD-XXXXXX). Use this for support correspondence until an AWB is assigned."
          },
          "reference_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "confirmed"
            ],
            "description": "Literal 'confirmed' = lead captured. Despite the wording, no dispatch occurs without phone confirmation."
          },
          "status_display": {
            "type": "string",
            "example": "Booking Confirmed - Pending Pickup"
          },
          "sender": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "phone": {
                "type": "string"
              },
              "city": {
                "type": "string"
              }
            }
          },
          "receiver": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string"
              },
              "country": {
                "type": "string"
              },
              "city": {
                "type": "string"
              }
            }
          },
          "shipment": {
            "type": "object",
            "properties": {
              "weight": {
                "type": "number"
              },
              "commodity": {
                "type": "string"
              },
              "carrier": {
                "type": "string"
              },
              "pieces": {
                "type": "integer"
              },
              "estimated_cost": {
                "type": "object",
                "properties": {
                  "amount": {
                    "type": "number"
                  },
                  "currency": {
                    "type": "string",
                    "enum": [
                      "INR"
                    ]
                  },
                  "note": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "next_steps": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Plain-text checklist of what happens next (team contact, AWB assignment, tracking)."
          },
          "webhook_url": {
            "type": "string",
            "description": "Note string about webhook configuration."
          }
        },
        "required": [
          "booking_id",
          "status",
          "status_display"
        ]
      }
    }
  }
}
