Skip to content

API ​

Navexa's external API lets your own systems read your locations and packages, bin-pack items, quote shipping rates and work with labels. (The checkout rates endpoint stores call is separate: see Checkout rates.)

Base URL: https://api.navexa.co/api/v1/external

Authentication ​

  1. In Navexa, open API tokens in the menu (owners and admins) and choose the scopes the token needs. An expiry is optional.
  2. Copy the token when it is shown. It is shown once; if you lose it, revoke it and create another.
  3. Send it as a bearer token on every request:
bash
curl https://api.navexa.co/api/v1/external/locations \
  -H "Authorization: Bearer nfx_..."

Tokens start with nfx_. A token acts for the account it was created in.

ScopeAllows
locationReading ship-from locations
shipping:ratesPackages, bin-packing and rate quotes
returns:labelsQuoting and buying customer return labels (spends money)
orders:labelsReading an order's labels and what they cost

A missing, malformed, expired or revoked token gets HTTP 401; a token without the scope an endpoint needs gets HTTP 403. Errors are JSON: { "error": "...", "message": "...", "code": "..." }.

Locations ​

GET /locations (scope location): your active ship-from locations.

json
[
  {
    "id": "<location id>",
    "name": "Main warehouse",
    "city": "Austin",
    "province": "TX",
    "zip": "78701",
    "country": "US",
    "is_active": true
  }
]

Packages ​

GET /packages (scope shipping:rates): your package templates (boxes, polybags, tubes, pallets). Dimensions in inches, weights in grams.

json
[
  {
    "id": "<package id>",
    "name": "Small box",
    "type": "box",
    "length_inches": 8,
    "width_inches": 6,
    "height_inches": 4,
    "weight_grams": 120
  }
]

Bin-pack items ​

POST /shipping/bin-pack (scope shipping:rates): which of your packages the items fit in, and how many boxes. Give a sku and Navexa uses the product's dimensions, or give the dimensions yourself. Up to 500 items and 10,000 units per request.

json
{
  "items": [
    { "id": "line-1", "name": "Widget", "quantity": 2, "sku": "WID-001" },
    {
      "id": "line-2",
      "name": "Poster tube",
      "quantity": 1,
      "weight_grams": 300,
      "length_inches": 24,
      "width_inches": 3,
      "height_inches": 3
    }
  ]
}
json
{
  "package_assignments": [
    {
      "package_name": "Small box",
      "length_inches": 8,
      "width_inches": 6,
      "height_inches": 4,
      "weight_grams": 1120,
      "item_count": 2
    }
  ],
  "skipped_items": [{ "item_id": "line-2", "name": "Poster tube", "reason": "..." }],
  "partial_quote": true,
  "total_packages": 1
}

partial_quote is true when some items were skipped; skipped_items says why.

Rates ​

POST /shipping/rates (scope shipping:rates): carrier rates for packages you describe, totalled per service. US addresses only for now; up to 100 packages; weights in ounces.

json
{
  "location_id": "<location id>",
  "destination": {
    "address1": "456 Oak Ave",
    "city": "New York",
    "state": "NY",
    "zip": "10001",
    "country": "US"
  },
  "destination_is_residential": true,
  "packages": [{ "length_inches": 8, "width_inches": 6, "height_inches": 4, "weight_oz": 18 }],
  "items": [{ "sku": "WID-001", "quantity": 2 }]
}
  • location_id is optional. Without it, Navexa ships from the location that has the items in stock, else your default location.
  • items is optional; with it, inventory_warnings lists SKUs the chosen location can't fully cover.
json
{
  "rates": [
    {
      "id": "USPS_GroundAdvantage_USD",
      "carrier": "USPS",
      "service": "GroundAdvantage",
      "service_code": "GroundAdvantage",
      "cost_cents": 742,
      "currency": "USD",
      "estimated_days": 3,
      "package_count": 1
    }
  ],
  "location_id": "<location id>",
  "quoted_at": "2026-11-03T17:00:00Z",
  "expires_at": "2026-11-03T17:30:00Z",
  "quote_id": "sq_..."
}

A carrier that fails to quote gets HTTP 502 with code RATE_PROVIDER_UNAVAILABLE.

Order labels ​

GET /orders/{orderId}/labels (scope orders:labels): the live labels bought for a Medusa order, by its Medusa order id. An empty list means nothing has shipped yet; an unknown order is HTTP 404.

json
{
  "labels": [
    {
      "tracking_number": "9400...",
      "carrier": "USPS",
      "postage_cents": 742,
      "insurance_cents": 0,
      "is_freight": false,
      "orders_on_shipment": 1,
      "purchased_at": "2026-11-03T17:05:00Z"
    }
  ]
}

postage_cents is null when the purchase recorded no price. orders_on_shipment above 1 means a consolidated box whose postage is shared with other orders.

Return labels ​

For a return created in Medusa, keyed by its Medusa return fulfillment id (scope returns:labels). The Navexa Medusa plugin uses these.

EndpointDoes
POST /returns/{fulfillmentId}/ratesQuotes the return label rates, cheapest first. Free. Optional body: { "order_id": "..." }
POST /returns/{fulfillmentId}/buy-labelBuys the rate you picked: { "rate_id": "..." }. Charges your account
POST /returns/{fulfillmentId}/cancelCancels the return, refunding its label first if one was bought

Buying twice, or with a rate from an expired quote, is refused with HTTP 409 (RETURN_LABEL_ALREADY_BOUGHT, RETURN_RATE_STALE). Get rates again and retry.

Questions? Chat with us from Help & support in the app, or email support@navexa.co.