Shipfast

ShipFast, now Velocity Shipping, an Indian multi-carrier aggregator with a public REST API, Bearer API keys, webhooks, NDR, RTO and COD remittance.

ShipFast is an Indian multi-carrier shipping aggregator. It is the same operation as Velocity Shipping: shipfast.in now serves a tracking page branded "Velocity Shipping" and links to velocity.in, tracking URLs returned by the API point at shipfastt.in/track/{awb} and velocityshipping.in/track/{awb}, and the API documentation's own sample warehouse uses the address shipfast-clickpost@velocity.in. The operating company is White Wizard Technologies Private Limited of Bengaluru, trading as Velocity, which also sells revenue-based financing, payments and AI voice calling to Indian direct to consumer brands. Unusually for this segment, the shipping API is fully documented in public, and it covers the whole carrier object set a commerce database needs: serviceability, rates, shipment creation with AWB and label, pickup, tracking with event history, NDR action, RTO, returns with quality check, shipment-level charges and COD remittance with UTR.

At a glance

What it is

Velocity is an Indian e-commerce growth platform founded in 2020, registered in Bengaluru as White Wizard Technologies Pvt. Ltd. Its published numbers are over 1,500 crore rupees disbursed to more than 4,000 direct to consumer brands. The product line is Finance (term loans, overdraft and revenue-based financing), Velocity Shipping, Vani AI (voice calling), Velocity Pay and Insights.

Velocity Shipping is the logistics arm and the subject of this page. It behaves like the other Indian aggregators: it holds relationships with the national carriers, applies shipping rules to allocate a carrier per shipment, books the AWB, prints the label, schedules pickup, runs an NDR workflow and remits COD money.

Carriers seen in the documentation's own serviceability samples: DTDC Standard, Ekart Standard, Delhivery Standard, Delhivery Standard 5 Kg, Bluedart Standard, XpressBees Standard and Pikndel NDD. Carriers are identified by an opaque carrier_id such as CARO0ZZQH1H6U, which is stable and is what you pass back to pin a shipment to a specific courier.

ClickPost lists ShipFast as an integrated carrier and describes it as "an India-based logistics platform specializing in same-day and next-day delivery", offering "shipping analytics, returns management, weight dispute handling, and automated shipping rules". ClickPost's own integration table claims support for order creation, cancellation, tracking by polling, tracking by webhook, proof of delivery, NDR, pickup request and label generation, with the AWB generated by ClickPost. That is a useful second opinion, and it matches what the first-party API actually exposes, except for proof of delivery, which is not a documented field on the Velocity API.

Note

The brand situation is worth pinning down before you write it into a catalogue. shipfast.in redirects to shipfast.in/track and renders a page titled "Shipment Tracking, Velocity Shipping". The API's tracking response returns track_url values on shipfastt.in (with a double t), while the webhook body returns tracking_url values on velocityshipping.in. Three domains, one product. Treat ShipFast as a Velocity Shipping brand, and expect tracking links in historical data to be split across all three hosts.

API access

Self-serve for anyone with a Velocity Shipping seller account. There is no partner programme, no approval queue and no published cost for API access itself.

Base URL: https://shazam.velocity.in/, with every documented path under /custom/api/v1/.

Versions. One version, v1. No deprecation schedule is published for the API as a whole. One component is explicitly deprecated: the auth-token session endpoint, marked as "deprecated and will be removed in a future release" with a recommendation to migrate to API keys.

SDKs. None published. The documentation is curl-based throughout. There is no OpenAPI specification, no Postman collection and no client library, so models have to be written by hand from the reference pages.

Standard error codes, published as a single table:

Note that 422 covers three quite different failures. A 422 on a shipment create is a carrier refusal and should be surfaced to an operator; a 422 on a cancel usually means the parcel has already been picked up and is no longer cancellable. Do not retry either blindly.

Authentication

API keys, the supported path

  1. In the Velocity Shipping dashboard go to Settings, API Keys.
  2. Click Generate API Key.
  3. Give the key a name and choose an expiry date. Keys can be valid for up to 365 days.
  4. Copy the token. It is shown only once.
  5. Send it on every request as Authorization: Bearer <token>.

The documentation is explicit: "Always include the Bearer prefix before the token value."

Key management, also from Settings, allows a key to be enabled or disabled temporarily without deletion, or deleted permanently. A maximum of 5 active API keys is allowed per account. The published advice is to name keys by their use, rotate regularly, and never put them in client-side code, logs or public repositories.

curl --location 'https://shazam.velocity.in/custom/api/v1/warehouse' \
  --header 'Content-Type: application/json' \
  --header 'Authorization: Bearer eyJhbGciOiJIUzI1NiJ9...' \
  --data '{...}'

Multi-seller handling is simple because nothing is negotiated: one key per seller account, stored against your channel_account_id. The 365 day maximum lifetime is the one thing that needs a calendar: put a rotation reminder at day 330, because there is no refresh flow and an expired key fails as a 401 with no warning.

Deprecated session token

Still functional, still documented, and marked for removal.

curl --location 'https://shazam.velocity.in/custom/api/v1/auth-token' \
  --header 'Content-Type: application/json' \
  --data-raw '{ "username": "+919866340090", "password": "YourPassword123" }'
{
  "token": "bbqRkOXw0xWLuYj9ubnDwg",
  "expires_at": "2025-09-17T10:11:40"
}

username is the seller's mobile number with country code, password is the dashboard password. Five consecutive failed attempts may temporarily lock the account, which makes this a bad thing to retry in a loop. Do not build on it.

Warning

The reference is internally inconsistent about the header format. The authentication page and the API index both say Authorization: Bearer <token>, but many endpoint samples send the bare token with no prefix, for example --header 'Authorization: bbqRkOXw0xWLuYj9ubnDwg'. Those samples appear to date from the deprecated session-token era. Use Bearer with an API key, as the authentication page instructs, and verify against a live key before assuming either form works everywhere.

Objects we can read

All paths are relative to https://shazam.velocity.in.

Orders

There is no separate order read. Orders exist as the order object nested inside a shipment, carrying id, order_date, channel, display_id, external_id and store_name. external_id is your own reference and is the join key back to a Shopify or WooCommerce connector.

Orders can be created without a shipment, see the write section, and then assigned a courier later.

Order items

Nested in the shipment as items[], with id, quantity, price, name and sku. On creation the item shape is richer: name, sku, units, selling_price, discount and tax.

Products and listings

None. This is a carrier aggregator with no catalogue.

Inventory

None.

Shipments and tracking

Three different reads, each with a different shape, and it is worth knowing which to use when.

Serviceability. POST /custom/api/v1/serviceability answers whether a pin code pair is shippable and which carriers can take it.

Request fields: from (pickup pincode), to (destination pincode), payment_mode (cod or prepaid), shipment_type (forward or return). All required.

{
  "result": {
    "serviceability_results": [
      { "carrier_id": "CAR0EPDPJXXL4", "carrier_name": "DTDC Standard" },
      { "carrier_id": "CARCVBWTPRH08", "carrier_name": "Ekart Standard" },
      { "carrier_id": "CAR5IXXJVT5MD", "carrier_name": "Delhivery Standard 5 Kg" },
      { "carrier_id": "CARO0ZZQH1H6U", "carrier_name": "Delhivery Standard" },
      { "carrier_id": "CAR2FZNOLGJ2X", "carrier_name": "Bluedart Standard" },
      { "carrier_id": "CARTS5SW8LSJT", "carrier_name": "XpressBees Standard" },
      { "carrier_id": "CARKX7WW6UNS8", "carrier_name": "Pikndel NDD" }
    ],
    "zone": "zone_a"
  },
  "status": "SUCCESS"
}

zone is the tariff zone and drives the price, so capture it.

Rates. POST /custom/api/v1/rates prices a shipment before it exists. Fields: journey_type (forward or return), origin_pincode, destination_pincode, dead_weight in grams, length, width and height in cm, all greater than zero; payment_method (cod or prepaid) is mandatory for forward, shipment_value is required when payment_method is cod, and qc_applicable applies to returns and defaults to true.

{
  "status": "SUCCESS",
  "result": {
    "serviceable_couriers": [
      {
        "carrier_id": "carrier-unique-id",
        "carrier_name": "Delhivery",
        "carrier_code": "DELHIVERY",
        "service_level": "standard",
        "is_fast": false,
        "is_prime": false,
        "status": "active",
        "expected_delivery": { "pickup": "2024-01-16", "delivery": "2024-01-19" },
        "charges": {
          "forward_freight_charges": 85.00,
          "cod_charges": 25.00,
          "rto_charges": 85.00,
          "total_forward_charges": 110.00
        },
        "platform_fee": 5.00
      }
    ],
    "shipment_details": {
      "journey_type": "forward",
      "origin_pincode": "400001",
      "destination_pincode": "110001",
      "payment_method": "cod",
      "dead_weight": 750.0,
      "volumetric_weight": 1500.0,
      "applicable_weight": 1500.0,
      "zone": "B"
    }
  }
}

This is the single most useful read on the platform for anyone doing shipping cost analysis: it returns dead weight, volumetric weight and the applicable weight that will actually be billed, per carrier, before the parcel moves. The return variant swaps in return_freight_charges, qc_charges and total_return_charges.

Tracking by AWB. POST /custom/api/v1/order-tracking with awbs[], a list of AWB numbers.

{
  "result": {
    "PD6786164": {
      "tracking_data": {
        "track_status": null,
        "shipment_status": "delivered",
        "shipment_track": [
          {
            "id": "8be85889-7f3d-4d68-81aa-14ab5d40ada9",
            "awb_code": "PD6786164",
            "courier_company_id": "CARKX7WW6UNS8",
            "shipment_id": "SHIRDNEL4I8PC",
            "order_id": "ORDVRLXCBRT4E",
            "pickup_date": "2025-07-30 16:20:31",
            "delivered_date": "2025-07-30 17:39:29",
            "weight": 0.3,
            "packages": 1,
            "current_status": "delivered",
            "delivered_to": "Bengaluru",
            "destination": "Bengaluru",
            "consignee_name": "Arun nayak",
            "origin": "Bangalore",
            "courier_agent_details": null
          }
        ],
        "shipment_track_activities": [
          { "date": "2025-07-30 17:39:29", "activity": "DELIVERED", "location": "Bengaluru" },
          { "date": "2025-07-30 17:38:18", "activity": "OUT FOR DELIVERY", "location": "Bengaluru" },
          { "date": "2025-07-30 16:20:31", "activity": "PICKED UP", "location": "Bengaluru" },
          { "date": "2025-07-30 16:20:30", "activity": "OUT FOR PICKUP", "location": "Bengaluru" }
        ],
        "track_url": "https://shipfastt.in/track/PD6786164"
      }
    }
  }
}

consignee_name and the address fields are PII. The activity strings are free text in upper case, not an enum; map them by the parent shipment_status, not by parsing the activity.

Bulk shipment listing. POST /custom/api/v1/shipments is the workhorse for a nightly sync. Pagination is page (1-indexed) and per_page (default 20, maximum 100), with a meta block carrying current_page, per_page, total and next_search_after. Date filtering uses date_field (order_date and others), start_time and end_time as Unix timestamps, with sort_order of asc or desc.

Filters are generous: search, status, granular_status[], carrier_id[], warehouse_ids[], channel[], zone[], is_cod, sku[], tags[], ndr_reason[], rto_reason[], applied_weight_min, applied_weight_max, attempts_count__gte and attempts_count__lte.

{
  "data": [
    {
      "id": "shipment-uuid",
      "type": "shipment",
      "attributes": {
        "tracking_number": "TRACK123456789",
        "status": "in_transit",
        "sub_status": null,
        "created_at": "2024-01-15T10:30:00Z",
        "warehouse_id": "warehouse-uuid",
        "total_price": 1500.00,
        "cod_amount": 0.00,
        "is_cod": false,
        "zone": "zone_b",
        "applied_weight": { "value": 750, "metric_unit": "g" },
        "attempt_count": 1,
        "carrier": { "id": "carrier-uuid", "name": "Delhivery" },
        "order": {
          "id": "order-uuid",
          "order_date": "2024-01-15T09:00:00Z",
          "channel": "shopify",
          "display_id": "ORD-12345",
          "external_id": "shopify-order-123",
          "store_name": "My Store"
        },
        "items": [
          { "id": "item-uuid", "quantity": 1, "price": 1500.00, "name": "Product Name", "sku": "SKU-001" }
        ],
        "shipping_address": {
          "name": "Customer Name",
          "phone": "9876543210",
          "city": "Delhi",
          "state": "Delhi",
          "zip": "110001"
        },
        "shipment_milestones": [
          { "milestone": "ready_for_pickup", "milestone_at": "2024-01-15T10:35:00Z" },
          { "milestone": "in_transit", "milestone_at": "2024-01-15T14:00:00Z" }
        ],
        "tracking_details": [
          {
            "id": "tracking-uuid",
            "status": "in_transit",
            "last_location": "Delhi Hub",
            "event_date_time": "2024-01-15T14:00:00Z",
            "description": "Shipment in transit"
          }
        ]
      }
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 156,
    "next_search_after": ["1705320000000", "shipment-uuid"]
  }
}

next_search_after is a cursor. Prefer it over incrementing page for large sweeps, because offset paging over a moving result set will skip rows.

Status vocabulary is published in full, which is rare and extremely useful.

Forward: pending, rejected, processing, ready_for_pickup, pickup_scheduled, not_picked, in_transit, out_for_delivery, delivered, ndr_raised, need_attention, reattempt_delivery, cancelled, lost, externally_fulfilled.

RTO: rto_initiated, rto_cancelled, rto_in_transit, rto_need_attention, rto_delivered, rto_lost.

Return: return_rejected, return_pickup_scheduled, return_not_picked, return_in_transit, return_delivered, return_cancelled, return_ndr_raised, return_need_attention, return_qc_failed, return_lost.

Note externally_fulfilled, which means the shipment was fulfilled by a courier outside the Velocity network. Those rows will have no tracking events, and treating them as stuck shipments is a classic false alarm.

Returns and cancellations

POST /custom/api/v1/returns lists return shipments, with the same page and per_page pagination as the forward listing. Filters include status, created_at__gte and created_at__lte as ISO 8601, qc_status[] (values seen: passed, pending), refund_status[] (values seen: pending, processed), warehouse_id[], channel[], sort_by and sort_order.

The quality check step is a genuine differentiator: an Indian return can fail QC on arrival, and return_qc_failed plus a qc_status filter lets you separate a return that was received from a return that was accepted. Refund status is tracked separately again, so you can find returns received, QC passed and refund still pending, which is the queue that generates customer complaints.

Cancellation of a forward shipment is a write, covered below.

Payments and settlements

Two finance endpoints, both POST, both taking up to 100 AWBs per call, both throttled to 5 requests per minute.

POST /custom/api/v1/shipping-charges returns the ledger of charge events per AWB, including order-level charges raised before an AWB was assigned.

{
  "data": [
    {
      "tracking_number": "AWB001",
      "charges": [
        { "event_type": "shipment_debit", "transaction_type": "debit", "amount_without_gst": 84.75, "amount_with_gst": 100.0 },
        { "event_type": "platform_fee", "transaction_type": "debit", "amount_without_gst": 8.47, "amount_with_gst": 10.0 },
        { "event_type": "oc_automation_ai_call_service_charge", "transaction_type": "debit", "amount_without_gst": 4.24, "amount_with_gst": 5.0 }
      ]
    },
    { "tracking_number": "AWB002", "error": "AWB AWB002 does not exist in your account." }
  ]
}

transaction_type is debit or credit, so weight-dispute reversals and refunds appear as credits against the same AWB. Both GST-inclusive and GST-exclusive amounts are given, which saves you guessing the tax treatment.

POST /custom/api/v1/cod-remittance closes the loop on cash on delivery.

{
  "data": [
    {
      "tracking_number": "AWB001",
      "delivery_date": "2026-07-20",
      "cod_amount": 499.0,
      "remittance_date": "2026-07-29",
      "utr_no": "UTR123456789"
    },
    { "tracking_number": "PREPAID001", "error": "AWB PREPAID001 is a prepaid order." },
    { "tracking_number": "MISSING001", "error": "AWB MISSING001 does not exist in your account." }
  ]
}

remittance_date is the actual settlement date once paid, or the next scheduled date per the seller's configuration if not. utr_no is the bank transaction reference, present once settled and null while pending. That is a per-shipment reconciliation key to bank statements, which very few platforms in this segment expose at all.

Both finance endpoints return HTTP 400 when awbs is missing, empty, not an array, or longer than 100.

Summary reports. POST /custom/api/v1/reports with start_date_time and end_date_time in ISO 8601 and shipment_type of forward or return returns a status-based summary for the window. Useful for a dashboard, not a substitute for the row-level listing.

Customers

No customer object. Buyer identity rides on each shipment as shipping_address with name, phone, city, state and zip, and on the tracking response as consignee_name. All PII, unmasked. On creation the full set is billing_customer_name, billing_last_name, billing_address, billing_city, billing_pincode, billing_state, billing_country, billing_email and billing_phone.

Locations

Warehouses. POST /custom/api/v1/warehouse creates a pickup location. Fields: name, phone_number, email, contact_person, optional gst_no, and address_attributes with street_address, zip, city, state and country. The response returns a short warehouse id:

{ "status": "SUCCESS", "payload": { "warehouse_id": "WH66DU" } }

Stores. A store is a sales channel with its own brand name and logo, used in customer-facing tracking notifications. This matters if you ship on behalf of several brands: one store per brand, and each buyer sees the right branding.

  • GET /custom/api/v1/client-stores lists them
  • GET /custom/api/v1/client-stores/{id} fetches one
  • POST /custom/api/v1/client-stores creates one, with store_name (unique per account, letters, numbers and hyphens only), optional brand_name and optional logo up to 2 MB
  • PUT /custom/api/v1/client-stores/{id} updates name or logo
{
  "data": [
    { "id": "uuid", "store_name": "my-website", "brand_name": "My Brand", "channel": "manual_order", "status": "connected" }
  ]
}

Stores created through the API are always manual_order channel stores. Pass store_name on a shipment create to link the order to a store; an unknown store_name is rejected rather than ignored.

Writing back: listings, price and stock

Not applicable, this is a carrier aggregator. There is no listing, no price and no stock on the platform. Anything in the unified listings or inventory tables comes from a sales channel connector.

The write path is shipment creation and cancellation, and it is unusually complete.

Create a forward shipment

POST /custom/api/v1/forward-order-orchestration does everything in one call: creates the order, validates serviceability, assigns a carrier (automatically per your shipping rules if carrier_id is blank), generates the AWB, prints the label and books the pickup.

Required fields: order_id (unique per order), order_date as YYYY-MM-DD HH:mm, the billing_* address block, print_label, order_items[], payment_method (COD or PREPAID), sub_total, cod_collectible (pass 0 for prepaid), length, breadth, height in cm, weight in kg, pickup_location and warehouse_id. Optional: carrier_id, shipping_is_billing, shipping_charges, rto_warehouse_id if RTO should go to a different warehouse, store_name, and a vendor_details block.

{
  "order_id": "ORDER-43242",
  "order_date": "2018-05-08 12:23",
  "carrier_id": "CARO0ZZQH1H6U",
  "billing_customer_name": "Saurabh",
  "billing_last_name": "Jindal",
  "billing_address": "Incubex, Velocity",
  "billing_city": "Bangalore",
  "billing_pincode": "560102",
  "billing_state": "Karnataka",
  "billing_country": "India",
  "billing_email": "saurabh@velocity.in",
  "billing_phone": "8860697807",
  "shipping_is_billing": true,
  "print_label": true,
  "order_items": [
    { "name": "T-shirt Round Neck", "sku": "t-shirt-round1474", "units": 2, "selling_price": 1000, "discount": 100, "tax": 10 }
  ],
  "payment_method": "COD",
  "sub_total": 990,
  "cod_collectible": 990,
  "shipping_charges": 49,
  "length": 100,
  "breadth": 50,
  "height": 10,
  "weight": 0.5,
  "pickup_location": "HomeNew",
  "warehouse_id": "WHZWUN",
  "rto_warehouse_id": "MYZYN"
}
{
  "status": 1,
  "payload": {
    "pickup_location_added": 1,
    "order_created": 1,
    "awb_generated": 1,
    "label_generated": 1,
    "pickup_generated": 1,
    "manifest_generated": 0,
    "pickup_scheduled_date": null,
    "pickup_booked_date": null,
    "order_id": "ORDKDKHOFL07I",
    "shipment_id": "SHIHB0BMT4DYM",
    "awb_code": "34812010700125",
    "courier_company_id": "CARO0ZZQH1H6U",
    "courier_name": "Delhivery Standard",
    "assigned_date_time": { "date": "2025-09-30T17:50:50.424+05:30", "timezone_type": 3, "timezone": "Asia/Kolkata" },
    "applied_weight": 0.5,
    "cod": 1,
    "label_url": "https://velocity-shazam-prod.s3.ap-south-1.amazonaws.com/...",
    "manifest_url": null,
    "routing_code": null,
    "rto_routing_code": null,
    "pickup_token_number": null,
    "charges": {
      "frwd_charges": { "shipping_charges": "44.40", "cod_charges": "31.30", "dead_weight_billing": true },
      "rto_charges": { "rto_charges": "40.00" }
    }
  }
}

The per-step flags (order_created, awb_generated, label_generated, pickup_generated, manifest_generated) make this call partially idempotent in practice: a zero tells you exactly which step failed rather than failing the whole call. Check each flag, do not just check status. dead_weight_billing: true tells you the charge was calculated on actual weight rather than volumetric, which is the thing weight disputes turn on.

Note the unit mismatch across the API: shipment creation takes weight in kilograms, the rates endpoint takes dead_weight in grams, and the shipment listing returns applied_weight as { "value": 750, "metric_unit": "g" }. Normalise to grams at the boundary or you will be out by a factor of a thousand.

Create order and shipment separately

POST /custom/api/v1/forward-order creates the order with no courier assigned and returns shipment_id and order_id with awb_generated: 0. Then POST /custom/api/v1/forward-order-shipment with { "shipment_id": "...", "carrier_id": "" } assigns the courier and returns the AWB and label_url. Leaving carrier_id empty applies the shipping rules. This split is the right shape when orders arrive long before they are packed.

Create a reverse pickup

POST /custom/api/v1/reverse-order-orchestration. The address blocks invert: pickup_* fields describe the customer the parcel is collected from, shipping_* fields describe the warehouse it goes to. Same carrier_id semantics, blank for automatic.

Cancel

POST /custom/api/v1/cancel-order with awbs[], up to 50 per call.

{ "message": "Bulk Shipment cancellation is in progress. Please wait for some time." }

Cancellation only works before pickup, and it is asynchronous, so the 200 is an acknowledgement rather than a result. Poll the shipment status or wait for the webhook to confirm cancelled.

NDR action

Only valid while a shipment is in ndr_raised.

POST /custom/api/v1/reattempt with awb required, plus optional updated_address (an object with address_line and landmark), updated_phone_number and comments.

{
  "awb": "38539510600353",
  "updated_address": { "address_line": "123 New Street", "landmark": "Near Park" },
  "updated_phone_number": "+919876543210",
  "comments": "Customer requested reattempt with updated address"
}

Two published constraints matter operationally: the pin code and city cannot be changed, only the street address and landmark, and a re-attempt may be refused if more than two delivery attempts have already been made or for specific NDR scenarios such as an OTP-verified cancellation. Phone number updates work only on selected couriers.

POST /custom/api/v1/initiate-rto with awb sends the parcel back instead. Both return { "success": true, "message": "...", "data": { "awb": "..." } }.

Webhooks and notifications

Configured in the dashboard, not by API: log in, Settings, Webhooks, add the endpoint URL, choose an authorisation method and supply the details.

Two authentication methods are offered: None, where no header is sent, and API Key, where the configured key is sent as X-API-Key. That is a shared secret in a header, not a signature over the body, so it proves the sender knows the key but does not protect against replay or tampering by anyone who has seen a request. Use HTTPS, keep the key long, and treat the body as a hint rather than as truth for anything financial.

Events published: order created, pickup scheduled, in transit, out for delivery, delivered, NDR raised, RTO initiated, RTO delivered, return pickup scheduled, return delivered, "and more". The event vocabulary is not exhaustively enumerated, but it tracks the status list above.

{
  "event": "status_change",
  "event_id": "fe629ee4-05af-499c-bd15-3ebb87d1a077",
  "event_timestamp": "2026-04-15T10:58:47+05:30",
  "data": {
    "shipment_id": "SHIQ6MAKJMOIY",
    "tracking_number": "41332221429154",
    "order_id": "ORDQ7LAKF9XJJ",
    "order_external_id": "510322643_602973822",
    "order_display_id": "114595804897851",
    "status": "delivered",
    "sub_status": "delivered",
    "carrier_name": "Delhivery Standard",
    "estimated_delivery_date": "2026-04-17T00:00:00+05:30",
    "original_edd": "2026-04-17T00:00:00+05:30",
    "shipment_type": "forward",
    "delivered_at": "2026-04-15T10:58:39+05:30",
    "tracking_url": "https://www.velocityshipping.in/track/41332221429154"
  }
}

Both estimated_delivery_date and original_edd are present, so you can measure promise drift without keeping your own history.

Published guidance: verify the X-API-Key header matches your configured key, use event_id for idempotency, return a 2xx within 5 seconds and do heavy work asynchronously, and fall back to polling the order details API if webhooks fail or are delayed. Five seconds is tighter than most platforms allow. No retry schedule is published, which is the reason the documented fallback exists; run a reconciliation sweep of POST /custom/api/v1/shipments filtered on a recent window rather than trusting delivery.

Rate limits and pagination

One published limit: the two finance endpoints, shipping-charges and cod-remittance, are "throttled to 5 requests per minute per authenticated client", returning HTTP 429 when exceeded. Both take up to 100 AWBs per call, so that is 500 AWBs per minute, or about 30,000 an hour, which is workable for a daily reconciliation but not for an unthrottled backfill. Batch to exactly 100 and pace at one call every 12 seconds.

No limit is published for any other endpoint. Do not assume there is none. Keep list sweeps to a few requests per second and back off on 429 and 5xx.

Other published caps:

  • cancel-order: 50 AWBs per request
  • shipping-charges and cod-remittance: 100 AWBs per request, duplicates silently deduplicated
  • shipments and returns: per_page maximum 100, default 20
  • API keys: 5 active per account, maximum 365 day lifetime

Pagination. page and per_page on the two listing endpoints, with a meta block giving current_page, per_page, total and next_search_after. Use next_search_after for anything larger than a few pages.

Mapping to the unified model

orders

order_items

listings, inventory

Not applicable.

shipments

returns

RTO is a different thing from a customer return and should be modelled separately: an rto_* shipment is the forward parcel coming back undelivered, with rto_charges billed on top of the forward freight.

settlements

customers

locations

Gaps and open questions

  • No sandbox. Nothing in the documentation describes a test mode or test keys. Every shipment create appears to be a real booking against a real carrier, which makes integration testing expensive. Ask for a test account before writing the connector.
  • No warehouse or store delete, and no warehouse list. Only create is documented for warehouses. How to enumerate existing warehouses by API is not published, yet warehouse_id is mandatory on every shipment create.
  • Rate limits are published for two endpoints only. The 5 per minute on the finance endpoints is stated. Everything else is silent, which is not the same as unlimited.
  • The Bearer prefix inconsistency. The authentication page and the API index require it, most endpoint samples omit it. Verify against a live key.
  • Proof of delivery. ClickPost's integration table claims POD support for ShipFast, but no POD field, signature, photo or receiver name beyond delivered_to and consignee_name appears in the Velocity API reference. The two sources disagree; POD may be a ClickPost-side capability rather than a Velocity API field.
  • No webhook retry policy. Published guidance says fall back to polling, which implies deliveries can be lost. Build the reconciliation sweep from day one.
  • Webhook authentication is a static header, not a signature. Adequate over HTTPS, but no replay protection and no body integrity.
  • Refund amounts on returns. refund_status is filterable, the amount is not exposed.
  • NDR and RTO reason vocabularies. Both are filterable arrays but neither is enumerated in the documentation. Collect the distinct values from live data before building a mapping table.
  • The ShipFast and Velocity Shipping relationship is inferred. No company page states that ShipFast is a Velocity brand. The evidence is the shared tracking page, the shipfastt.in tracking URLs returned by the API, and the shipfast-clickpost@velocity.in sample address in the official documentation. Strong, but confirm with the vendor before writing it into a contract.
  • Three tracking domains. shipfast.in, shipfastt.in and velocityshipping.in all appear. Which is canonical, and whether the older ones will keep resolving, is not stated.

Sources