Langsung ke konten

Developer

API STOWAGE.ID

Hitung rencana muat, kirim RFQ freight, dan terima webhook dari sistem Anda sendiri. REST + JSON, satuan mm & kg, dokumentasi OpenAPI 3.1.

Mulai cepat

  1. Buat kunci di dashboard → API & webhook (paket Business atau Enterprise) dan pilih scope yang dibutuhkan.

  2. Kirim permintaan ke base URL dengan header Authorization: Bearer stw_live_…

  3. Daftarkan endpoint webhook untuk plan.completed dan quote.received, lalu verifikasi tanda tangannya.

Base URL

https://api.stowage.id/v1/ext

Autentikasi

Setiap permintaan memakai kunci API organisasi di header Authorization. Kunci hanya tampil sekali dan kami menyimpannya sebagai hash SHA-256 — simpan di server, jangan di kode front-end atau aplikasi mobile.

Authorization: Bearer stw_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

Scope

plans:readMembaca plan
plans:writeMenghitung & menyimpan plan
rfqs:readMembaca RFQ & penawaran
rfqs:writeMembuat RFQ freight
ports:readMencari pelabuhan (UN/LOCODE)
vessel:readMembaca plan
vessel:writeMembaca plan

Batas laju & kuota

Batas laju dihitung per kunci dengan jendela geser 1 menit; kuota panggilan per organisasi per bulan kalender. Melebihi batas laju → 429 dengan header Retry-After; kuota habis atau paket tanpa API → 402.

PaketPermintaan/menit per kunciPanggilan/bulan
Free · Day Pass · Starter · Professional Tidak tersedia Tidak tersedia
Business 60 10.000
Enterprise 600 Tak terbatas

Solve sinkron dibatasi 500 unit per permintaan; muatan yang lebih besar memakai Optimasi Lanjutan di Studio (respons 413).

Format error

Semua error memakai bentuk yang sama; kode HTTP: 400 input tidak valid, 401 kunci salah, 402 paket/kuota, 403 scope, 404 tidak ditemukan, 413 terlalu besar, 429 batas laju.

{
  "error": {
    "code": "paywall",
    "message": "Monthly API call quota exhausted",
    "paywall": { "moment": "feature", "quota": "api_calls", "limit": 10000, "used": 10000, "suggestedPlan": "enterprise" }
  }
}

Contoh

curl

curl -X POST https://api.stowage.id/v1/ext/plans/solve \
  -H "Authorization: Bearer $STOWAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "PO 4711",
    "space": { "code": "40HC" },
    "items": [
      { "sku": "CARTON-A", "l": 600, "w": 400, "h": 400, "kg": 12, "qty": 40 },
      { "sku": "CRATE-B", "l": 1200, "w": 800, "h": 1000, "kg": 300, "qty": 6, "stackable": false }
    ],
    "save": true
  }'
curl "https://api.stowage.id/v1/ext/ports?q=Tanjung%20Priok" \
  -H "Authorization: Bearer $STOWAGE_API_KEY"

TypeScript SDK

Klien TypeScript tanpa dependensi (fetch + WebCrypto) untuk Node 18+, Deno, Bun, dan Cloudflare Workers. stowage-ext.ts

import { createExtClient } from './stowage-ext'; // /developers/stowage-ext.ts

const stowage = createExtClient({ apiKey: process.env.STOWAGE_API_KEY! });

const { result, plan } = await stowage.solve({
  space: { code: '40HC' },
  items: [{ sku: 'CARTON-A', l: 600, w: 400, h: 400, kg: 12, qty: 40 }],
  save: true,
});
console.log(result.utilizationPct, plan?.appUrl);

const { rfq } = await stowage.createRfq({
  title: 'Kopi ke Singapura', originLocode: 'IDTPP', destLocode: 'SGSIN', mode: 'LCL', incoterm: 'FOB',
  readyDate: '2026-12-01', planId: plan?.id, contact: { name: 'Ops', email: 'ops@example.com' },
});

Simulasi perencanaan muatan. Bukan pengganti penimbangan resmi, VGM, loading computer kapal, atau persetujuan operator.

Referensi endpoint

Dibuat otomatis dari spesifikasi OpenAPI 3.1. openapi.json

POST /plans/solve Plans

Hitung rencana muat

Menyusun kargo ke satu ruang muat secara sinkron dengan heuristik Stowage (maks. 500 unit). Dengan save: true hasil disimpan sebagai plan dan webhook plan.completed dikirim. Scope: plans:write.

Body permintaan · SolveRequest

  • title string
  • space Space · wajib
  • items CargoItem[] · wajib
  • options { minSupport, respectUnloadOrder, heavyBottom }
  • save boolean — Store as a load plan (plans quota) and emit plan.completed. Default false
  • includePlacements boolean — Return every placement. Default true

Respons

  • 200 Packing result
  • 400 Invalid input (validation_error)
  • 401 Missing or revoked API key (api_key_required)
  • 402 Plan without the API feature, or the monthly api_calls quota is used up (paywall)
  • 403 The key lacks the scope this endpoint needs (forbidden)
  • 413 More than 500 units (too_many_units): use Advanced Optimisation in the Studio
  • 429 Rate limit for this key exceeded (rate_limited); see Retry-After
GET /plans/{id} Plans

Ambil plan

Ringkasan plan tersimpan milik organisasi Anda; ?document=1 menambahkan dokumen lengkap (barang, penempatan, langkah muat). Scope: plans:read.

Parameter

  • id path · string · wajib
  • document query · "0" | "1" | "true" | "false"

Respons

  • 200 The plan
  • 400 Invalid input (validation_error)
  • 401 Missing or revoked API key (api_key_required)
  • 402 Plan without the API feature, or the monthly api_calls quota is used up (paywall)
  • 403 The key lacks the scope this endpoint needs (forbidden)
  • 404 No such plan in your organisation
  • 429 Rate limit for this key exceeded (rate_limited); see Retry-After
POST /rfqs Freight RFQ

Buat RFQ freight

Mengirim permintaan penawaran ke vendor terverifikasi yang cocok. Penawaran datang lewat webhook quote.received. Scope: rfqs:write.

Body permintaan · RfqRequest

  • title string · wajib
  • originLocode string · wajib
  • destLocode string · wajib
  • mode "FCL" | "LCL" | "air" | "truck" · wajib
  • incoterm "EXW" | "FCA" | "FAS" | "FOB" | "CFR" | "CIF" | "CPT" | "CIP" | "DAP" | "DPU" | "DDP" · wajib
  • readyDate string · wajib — YYYY-MM-DD, within 12 months
  • planId string — Attach a saved plan: cargo is computed from it
  • cargo { cbm, kg, packages, containers, description }
  • commodity string
  • notes string
  • contact { name, email, phone } · wajib
  • expiresInDays integer

Respons

  • 201 Created
  • 400 Invalid input (validation_error)
  • 401 Missing or revoked API key (api_key_required)
  • 402 Plan without the API feature, or the monthly api_calls quota is used up (paywall)
  • 403 The key lacks the scope this endpoint needs (forbidden)
  • 404 planId not found in your organisation
  • 429 Rate limit for this key exceeded (rate_limited); see Retry-After
GET /rfqs/{id} Freight RFQ

Ambil RFQ beserta penawaran

Status RFQ organisasi Anda dan penawaran yang sudah masuk (nominal dalam satuan terkecil mata uang). Scope: rfqs:read.

Parameter

  • id path · string · wajib

Respons

  • 200 The RFQ
  • 400 Invalid input (validation_error)
  • 401 Missing or revoked API key (api_key_required)
  • 402 Plan without the API feature, or the monthly api_calls quota is used up (paywall)
  • 403 The key lacks the scope this endpoint needs (forbidden)
  • 404 No such RFQ in your organisation
  • 429 Rate limit for this key exceeded (rate_limited); see Retry-After
GET /ports Reference data

Cari pelabuhan (UN/LOCODE)

Cari berdasarkan nama atau UN/LOCODE; tanpa q daftar pelabuhan kurasi Indonesia tampil lebih dulu. Scope: ports:read.

Parameter

  • q query · string
  • country query · string
  • seaport query · "0" | "1" | "true" | "false"
  • limit query · string — 1–20, default 20

Respons

  • 200 Matching ports
  • 400 Invalid input (validation_error)
  • 401 Missing or revoked API key (api_key_required)
  • 402 Plan without the API feature, or the monthly api_calls quota is used up (paywall)
  • 403 The key lacks the scope this endpoint needs (forbidden)
  • 429 Rate limit for this key exceeded (rate_limited); see Retry-After
GET /containers Reference data

Katalog kontainer & armada

Kontainer ISO, truk Indonesia, palet, dan ULD udara dengan dimensi dalam (mm) dan muatan (kg). Kodenya dipakai di /plans/solve. Semua kunci valid.

Parameter

  • kind query · "containers" | "trucks" | "pallets" | "ulds"

Respons

  • 200 Catalog
  • 400 Invalid input (validation_error)
  • 401 Missing or revoked API key (api_key_required)
  • 402 Plan without the API feature, or the monthly api_calls quota is used up (paywall)
  • 403 The key lacks the scope this endpoint needs (forbidden)
  • 429 Rate limit for this key exceeded (rate_limited); see Retry-After
POST /integrations/{partner}/plans Partner integrations

Mitra: buat plan di organisasi yang terhubung

Untuk Kontainer.id dan Logistikku.id. Diautentikasi dengan integrasi yang disetujui pelanggan (persetujuan + scope) dan tanda tangan HMAC atas body mentah — tanpa API key. Mode sandbox sampai kredensial produksi mitra terbit. Butuh scope integrasi plans:write.

Parameter

  • partner path · "kontainer_id" | "logistikku_id" · wajib
  • stowage-integration header · string · wajib — Integration id (`int_…`) the customer granted you
  • stowage-signature header · string · wajib — `t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`

Body permintaan · PartnerPlanRequest

  • title string · wajib
  • externalRef string — Your shipment / booking reference
  • space Space · wajib
  • items CargoItem[] · wajib

Respons

  • 201 Plan created
  • 400 Invalid input
  • 401 Unknown integration or bad signature (bad_signature)
  • 402 The organisation has no plan quota left
  • 403 Integration revoked or scope not granted
  • 409 Signature already used (replayed)
GET /integrations/{partner}/me Partner integrations

Mitra: identitas bersama pengguna yang memberi izin

Nama, e-mail, dan organisasi pengguna yang menghubungkan integrasi — hanya dengan scope profile:read yang ia setujui. Tanda tangan atas body kosong.

Parameter

  • partner path · "kontainer_id" | "logistikku_id" · wajib
  • stowage-integration header · string · wajib — Integration id (`int_…`) the customer granted you
  • stowage-signature header · string · wajib — `t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>.<raw body>")>`

Respons

  • 200 Identity
  • 401 Unknown integration or bad signature
  • 403 Integration revoked or scope not granted

Webhook

Kami mengirim POST JSON ke endpoint Anda. Balas 2xx dalam 10 detik; selain itu kami mencoba ulang hingga 7 kali dalam ±9 jam (30 dtk, 2 mnt, 10 mnt, 30 mnt, 2 jam, 6 jam). Log setiap pengiriman ada di dashboard.

Event

plan.completed

Plan selesai

Dikirim saat plan disimpan lewat API (save: true), oleh integrasi mitra, atau saat Optimasi Lanjutan selesai. Verifikasi Stowage-Signature sebelum memercayai isinya.

data: { planId, title, mode, spaceCode, version, utilizationPct, weightUtilizationPct, totalKg, itemCount, unitCount, source, appUrl }

quote.received

Penawaran vendor masuk

Dikirim saat vendor mengirim atau merevisi penawaran pada RFQ Anda.

data: { rfqId, rfqTitle, quoteId, vendorId, vendorName, amountMinor, currency, transitDays, validUntil, revised }

Verifikasi tanda tangan

Header Stowage-Signature berisi t=<unix detik>,v1=<hex>. Hitung HMAC-SHA256 dengan secret endpoint atas string "<t>.<body mentah>", bandingkan dengan v1 secara constant-time, dan tolak bila t berselisih lebih dari 5 menit.

Stowage-Signature: t=1767225600,v1=5f2b…c9e1
import { verifyWebhookSignature } from './stowage-ext';

// Express: app.post('/webhooks/stowage', express.raw({ type: 'application/json' }), handler)
const ok = await verifyWebhookSignature({
  secret: process.env.STOWAGE_WEBHOOK_SECRET!, // whsec_…
  body: req.body.toString('utf8'),             // RAW body, before JSON.parse
  header: req.header('Stowage-Signature'),
});
if (!ok) return res.status(400).end();
const event = JSON.parse(req.body.toString('utf8')); // { id, type, createdAt, orgId, data }
import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret: string, rawBody: string, header: string, toleranceSec = 300): boolean {
  const parts = Object.fromEntries(header.split(',').map((p) => p.split('=') as [string, string]));
  const t = Number(parts.t);
  if (!t || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
  return parts.v1?.length === expected.length && timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
}

Integrasi Kontainer.id & Logistikku.id

Mitra mengirim plan ke organisasi yang sudah memberi izin (persetujuan + scope) memakai header Stowage-Integration dan Stowage-Signature atas body mentah — tanpa kunci API. Identitas bersama hanya dengan scope profile:read. Saat ini mode sandbox.

BODY='{"title":"Booking KTR-991","space":{"code":"20GP"},"items":[{"sku":"BOX","l":1000,"w":1000,"h":1000,"kg":100,"qty":4}]}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$INTEGRATION_SECRET" -hex | sed 's/^.* //')
curl -X POST https://api.stowage.id/v1/ext/integrations/kontainer_id/plans \
  -H "Content-Type: application/json" \
  -H "Stowage-Integration: $INTEGRATION_ID" \
  -H "Stowage-Signature: t=$T,v1=$SIG" \
  -d "$BODY"

Widget embed

Pasang kalkulator CBM, mini planner, dan tombol "Minta penawaran" di situs vendor Anda. Gratis dengan tautan "Powered by Stowage.id"; white-label di Vendor Premium. Lead masuk ke inbox vendor; domain dibatasi allowlist.

<script src="https://embed.stowage.id/v1.js" data-vendor="wdg_…" data-locale="id" async></script>

Atribut opsional: data-locale="id|en" dan data-target="#selector". Untuk SPA: window.StowageWidget.mount(el, { widget }).

Stowage.id mempertemukan pengguna dan vendor; kontrak layanan antara pengguna dan vendor.

Buat widget di dashboard vendor

Untuk Vendor