Shipping

The shipping resource is the orchestration layer between merchants, the Biteship aggregator, and Fulkruma's local shipment objects. It owns:

  • The merchant's shipping origin (separate from warehouse addresses — the address Biteship gives to drivers).
  • The courier catalog — the live list of couriers and services Biteship supports.
  • Rate quotes — ask Biteship for prices and ETAs for a given destination + items combination.
  • Area search for postal-code lookup at checkout.
  • The Biteship config — the merchant's own Biteship API key (BYO) plus default-courier settings.

For the shipment objects themselves (status, events, label, cancel, tracking), see Shipments.

All requests on this page must be signed — see Authentication.

Endpoints

Method Path Purpose
GET /api/v1/shipping/origin Read the merchant's shipping origin
PATCH /api/v1/shipping/origin Set or update the shipping origin
GET /api/v1/shipping/couriers List the live Biteship courier catalog
GET /api/v1/shipping/areas?q=... Search Biteship areas (postal-code lookup)
POST /api/v1/shipping/rates Quote rates for a destination + items
GET /api/v1/shipping/config Read the merchant's Biteship config
PUT /api/v1/shipping/config Update the Biteship config

Read shipping origin

GET /api/v1/shipping/origin

Returns the merchant's configured shipping origin. If unconfigured, returns an envelope with all fields null and configured: false:

{
  "data": {
    "address": "Jl. Sudirman 123",
    "province": "DKI Jakarta",
    "city": "Jakarta Pusat",
    "district": "Setiabudi",
    "village": "Karet Tengsin",
    "postal": "10220",
    "areaId": "IDNP6IDNC148IDND1116IDZ12940",
    "lat": -6.2088,
    "lng": 106.8456,
    "note": "Ring the bell twice.",
    "contactName": "Warehouse",
    "contactPhone": "+628111111111",
    "couriers": ["jne", "sicepat", "anteraja", "jnt", "gojek", "grab"],
    "configured": true
  },
  "error": null,
  "meta": { ... }
}

Update shipping origin

PATCH /api/v1/shipping/origin

Sets or updates the origin in one call (upserts; create-or-replace semantics on the underlying BiteshipConfig row). Updating the address invalidates the cached Biteship "Location" mirror — the next outbound shipment recreates it lazily.

Request body

Field Type Required Description
address string (1–500) yes Street address line.
contactName string (1–100) yes Pickup contact name.
contactPhone string (1–30) yes Pickup contact phone.
province string no Province name (free-form; Biteship matches loosely).
city string no City.
district string no Kecamatan.
village string no Kelurahan.
postal string (≤20) no Postal code.
areaId string no Biteship area ID — look up via /api/v1/shipping/areas. Required for rate-quote accuracy in some regions.
lat number no Latitude. Required if couriers includes any instant-delivery code (gojek, grab, etc.).
lng number no Longitude. Required if couriers includes any instant-delivery code.
note string no Free-form note for the driver.
couriers string[] no Enabled courier codes for this merchant. Validated against the live Biteship catalog with a 1-hour cache — passing an unknown code returns 400 VALIDATION_ERROR.

Instant couriers require coords. Codes whose every service tier is instant or same_day (e.g. gojek, grab) reject orders without origin lat/lng. PATCHing the origin with such a code but no coords returns 400 VALIDATION_ERROR: Instant couriers require origin lat/lng.

Errors

Status error.code When
400 VALIDATION_ERROR Missing required field, unknown courier code, or instant courier without coords.
403 NO_ACCOUNT Token has no accountId.
fulkruma_curl PATCH '/api/v1/shipping/origin' \
  '{"address":"Jl. Sudirman 123","city":"Jakarta","postal":"10220","contactName":"Warehouse","contactPhone":"+62811111111","couriers":["jne","sicepat"]}'

List couriers

GET /api/v1/shipping/couriers

Returns the live Biteship courier catalog. Each row is one courier + service-tier combination; the same courier code can appear multiple times across service_type values (standard, same_day, instant, etc.).

{
  "data": [
    { "courier_code": "jne", "courier_name": "JNE", "courier_service_code": "reg", "courier_service_name": "Regular", "service_type": "standard", "tier": "regular" },
    { "courier_code": "gojek", "courier_name": "Gojek", "courier_service_code": "instant", "courier_service_name": "Instant", "service_type": "instant", "tier": "regular" }
  ],
  "error": null,
  "meta": { ... }
}

Search areas

GET /api/v1/shipping/areas?q=...

Searches Biteship's area catalog by free-form query (q ≥ 2 chars). Returns up to ~10 hits per query. Use the id from this response as the areaId on origin/destination addresses for the most accurate rate quotes.

{
  "data": [
    {
      "id": "IDNP6IDNC148IDND1116IDZ12940",
      "name": "Karet Tengsin, Setiabudi, Jakarta Pusat, DKI Jakarta",
      "country_name": "Indonesia",
      "administrative_division_level_1_name": "DKI Jakarta",
      "administrative_division_level_2_name": "Jakarta Pusat",
      "administrative_division_level_3_name": "Setiabudi",
      "administrative_division_level_4_name": "Karet Tengsin",
      "postal_code": 10220
    }
  ],
  "error": null,
  "meta": { ... }
}

Rate quote

POST /api/v1/shipping/rates

Quotes courier rates for the given destination + items combination, using the merchant's configured shipping origin and enabled couriers.

Request body

Field Type Required Description
destination object yes See the destination shape.
items array yes At least one item. See the item shape.
insurance boolean no Include insured pricing tiers in the response.

Response — 200 OK

{
  "data": {
    "rates": [
      {
        "courierCode": "jne",
        "courierName": "JNE",
        "courierServiceCode": "reg",
        "courierServiceName": "Regular",
        "courierType": "standard",
        "price": 18000,
        "etaMin": 2,
        "etaMax": 4,
        "etaUnit": "days",
        "insurance": 0
      }
    ],
    "count": 6,
    "hasCoords": true
  },
  "error": null,
  "meta": { ... }
}

If destination.lat/lng are absent, instant-courier rates are silently filtered out and hasCoords is false. The merchant's dashboard uses this flag to nudge the buyer to add precise coords.

Errors

Status error.code When
400 VALIDATION_ERROR Missing required field.
400 NOT_CONFIGURED Merchant has not configured the shipping origin yet.

The destination shape

Field Type Required Description
contactName string (1–100) yes Recipient name.
contactPhone string (1–30) yes Recipient phone. Used by drivers.
email string no Recipient email for tracking-page notifications.
address string (1–500) yes Street address line.
note string no Delivery instructions.
postalCode string no Postal code.
areaId string no Biteship area ID. Use /shipping/areas?q=… to find.
lat number no Latitude. Required for instant couriers to appear in quotes.
lng number no Longitude.

The item shape

Field Type Required Description
name string (1–200) yes Item name (appears on courier manifest).
value integer (≥0) yes Item value in IDR cents. Used for insurance and customs.
weight integer (≥1) yes Weight in grams.
quantity integer (≥1) yes Number of units.
description string no Free-form description.
category string no Biteship item category (e.g. food, electronics).
length integer no Centimetres.
width integer no Centimetres.
height integer no Centimetres.
productId string no Linked Fulkruma product.

Biteship config

GET /api/v1/shipping/config
PUT /api/v1/shipping/config

The Biteship config sits beside the shipping origin on the same BiteshipConfig row. The endpoints split write surface for clarity:

  • /shipping/origin — address, contact, enabled couriers.
  • /shipping/config — API key (BYO Biteship), defaultOriginId, defaultCourier, active flag.

Request body (PUT /config)

Field Type Description
apiKey string | null BYO Biteship API key. Pass null to remove and fall back to Fulkruma's platform key. Stored encrypted; only the last 4 chars are returned on read.
defaultOriginId string | null Biteship Location ID to use as origin (override the address-based mirror).
enabledCouriers string[] Override the courier whitelist.
defaultCourier string | null Pre-selected courier at checkout.
active boolean Master kill-switch for the shipping integration.
{
  "data": {
    "config": {
      "accountId": "acc_01HX...",
      "apiKeyConfigured": true,
      "apiKeyPreview": "…ab12",
      "defaultOriginId": null,
      "enabledCouriers": ["jne", "sicepat"],
      "defaultCourier": "jne",
      "active": true
    },
    "couriers": ["jne", "sicepat", "anteraja", "jnt", "gojek", "grab"]
  },
  "error": null,
  "meta": { ... }
}

Events

The shipping resource itself does not emit dedicated events. Lifecycle events fire under the shipments resource.

See Webhooks for the envelope and signature recipe.

Pattern 2: partner-managed shipping

When a Forjio partner (Storlaunch, Ripllo) calls Fulkruma on behalf of a merchant, the partner passes X-Fulkruma-On-Behalf-Of: acc_<merchantId> — all of the above endpoints then operate on that merchant's BiteshipConfig. The partner cannot read or set the merchant's encrypted Biteship API key via the API (returns apiKeyConfigured only); only the merchant can rotate it from their dashboard.

Next