—
Contoh di bawah memakai API key & ID merchant asli akun Anda. Simpan di server backend — jangan di frontend publik.
Panduan integrasi khusus merchant Anda. Base URL:
{{BASE_URL}} — kredensial sudah terisi di setiap contoh.
Domestik Indonesia aktif. Ekspor/internasional belum diaktifkan di production.
{{ORIGIN_POSTAL}}
OpenAPI JSON
FN Shipping adalah gateway pengiriman FN CREATIVE. Website/toko Anda memanggil API ini
dari server backend — bukan dari browser publik. Satu akun FN CREATIVE
(pay.fncreative.co.id) dipakai untuk payment dan shipping.
areas[].id + kode pos (bukan pos saja)id, postal_code, district dari GET /shipping/areas.
Kirim area_id (alias receiver_area_id) bersama postal_code ke
/shipping/quote dan destination.area_id ke /shipping/orders.
Kurir domestik mengurutkan paket menurut kecamatan (area_id) — hanya kode pos saja berisiko salah hub.
x-api-key + id_merchant di .env server website Anda.GET /shipping/wallet/balance — pastikan cukup sebelum quote & buat resi.GET /shipping/areas — simpan areas[].id + kode pos + kecamatan.POST /shipping/quote → tampilkan tarif → customer pilih kurir.POST /shipping/orders dengan rate_id dari langkah 4.GET /shipping/orders/:id/tracking.GET /shipping/orders/:id/label (HTML) atau /label.pdf (PDF).{{ORIGIN_POSTAL}}, berat default {{PACKAGE_WEIGHT}} gram.
Panduan singkat untuk merchant/toko yang baru bergabung FN Shipping — dari akun aktif sampai resi pertama tercetak.
Contoh implementasi live: Dirty Klab, KRAZZYDROOGS.50.
Merchant lain (website sendiri) mengikuti dokumentasi API yang sama — bagian
POST /shipping/orders & detail items[] di bawah.
origin_postal_code, kota asal, berat default paket..env di bawah).POST /shipping/orders.waybill_id muncul → unduh /label.pdf → tempel ke paket..env server merchant# FN Shipping (backend only)
FN_SHIPPING_API_URL={{BASE_URL}}
FN_MERCHANT_ID={{MERCHANT_ID}}
FN_API_KEY={{API_KEY}}
SHIPPING_VIA_FN_GATEWAY=true
# Opsional — branding label lokal jika fallback tanpa proxy gateway
BRAND_NAME=Nama Toko Anda
| Item | Fungsi |
|---|---|
id_merchant + x-api-key | Auth API dari backend toko |
| Dashboard shipping | Profil asal kirim, riwayat resi, top-up wallet |
| Quote multi-kurir | JNE, J&T, SiCepat, dll. — harga sudah final (termasuk layanan FN) |
| Resi kurir asli | waybill_id dari jaringan kurir via jaringan kurir terintegrasi |
| Label enterprise | PDF/HTML 100×150 mm — desain sama semua merchant, nama toko sebagai pengirim |
Base URL production: {{BASE_URL}}
| Method | Path | Auth | Fungsi |
|---|---|---|---|
GET | /health | — | Status layanan |
POST | /auth/login | — | Login dashboard (email & password) |
GET | /auth/me | Bearer | Data merchant & API key |
GET | /shipping/profile | Bearer | Baca profil shipping |
PUT | /shipping/profile | Bearer | Simpan profil shipping |
GET | /shipping/wallet/balance | API key atau Bearer | Cek saldo wallet (server integrasi) |
POST | /shipping/quote | API key atau Bearer | Hitung ongkir / COD |
GET | /shipping/areas | API key atau Bearer | Cari wilayah + kode pos otomatis |
GET | /shipping/orders | API key atau Bearer | Daftar riwayat resi merchant |
POST | /shipping/orders | API key atau Bearer | Buat resi pengiriman |
GET | /shipping/orders/:id | API key atau Bearer | Detail pesanan pengiriman |
GET | /shipping/orders/:id/tracking | API key atau Bearer | Lacak paket & riwayat status |
GET | /shipping/orders/:id/label | API key atau Bearer | Label resi HTML (siap cetak) |
GET | /shipping/orders/:id/label.pdf | API key atau Bearer | Label resi PDF (100×150 mm) |
x-api-key + id_merchant bersama-sama (disarankan untuk integrasi backend).
Bearer = token dari POST /auth/login (dashboard, profil, dan bisa juga dipakai untuk quote/order/label).
https://ship.fncreative.co.id · API: https://ship.fncreative.co.id/api.
URL lama https://ship.fncreative.org/api tetap aktif (alias). Kredensial payment gateway: https://pay.fncreative.co.id/api (alias pay.fncreative.org/api).
Gunakan header berikut di setiap request API dari server backend website Anda:
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
Content-Type: application/json
pay.fncreative.co.id, API pay.fncreative.co.id).| Endpoint | Header yang dipakai |
|---|---|
/shipping/profile, /shipping/wallet, /shipping/onboarding, /shipping/couriers (dashboard) | Authorization: Bearer <session_token> |
/shipping/wallet/balance, /shipping/quote, /shipping/areas, /shipping/couriers/catalog, /shipping/orders, /shipping/orders/* | x-api-key + id_merchant atau Bearer session |
pay.fncreative.co.id/api/shipping/quote & .../areas | Alias — diteruskan ke FN Shipping (header sama). Order/wallet/profil hanya di ship.fncreative.co.id |
/auth/login, /health | Tidak perlu auth |
Cara A — API key (disarankan production backend):
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
Cara B — Bearer session (cocok untuk dashboard / script internal):
Authorization: Bearer <session_token_dari_POST_/auth/login>
id_merchant — merchant diidentifikasi dari sesi login.
Untuk website publik, selalu gunakan Cara A via backend proxy.
x-api-key di JavaScript frontend publik, mobile app tanpa proteksi,
atau repository publik. Panggilan API shipping harus melalui backend Anda.
Merchant tidak memegang API key Biteship. FN CREATIVE menghubungkan akun Biteship di server
(ship.fncreative.co.id) — tarif, maps/areas, order, tracking, dan webhook.
GET /shipping/areas?input=kecamatan, kota, provinsi → simpan areas[].idPUT /shipping/profile — origin_postal_code + origin_area_id (asal)POST /shipping/quote — kirim area_id / receiver_area_id (tujuan), kota, provinsi, kode posrates[].id (format fn:jnt:ez) → setelah lunas POST /shipping/ordersGET /shipping/orders/:id/tracking · cancel · label PDF/HTMLpay.fncreative.co.id/api/shipping/* diteruskan ke server yang sama —
untuk wallet, profil, dan buat resi selalu gunakan base URL ship.
Profil disimpan per merchant. Server memakai profil ini saat request quote
tidak mengirim site_config atau item.
/api/shipping/profile
Baca profil shipping merchant yang sedang login
Authorization: Bearer <session_token>
{
"status": true,
"data": {
"origin_postal_code": "{{ORIGIN_POSTAL}}",
"origin_city": "{{ORIGIN_CITY}}",
"origin_province": "{{ORIGIN_PROVINCE}}",
"origin_label": "{{ORIGIN_LABEL}}",
"couriers": "{{COURIERS}}",
"package_weight_grams": {{PACKAGE_WEIGHT}},
"default_item_value": {{DEFAULT_ITEM_VALUE}},
"default_item_name": "{{DEFAULT_ITEM_NAME}}",
"cod": {
"enabled": false,
"fee": 0,
"local_city": "",
"area_note": "",
"eta": "Koordinasi via WhatsApp"
},
"profile_ready": {{PROFILE_READY}},
"updated_at": "..."
}
}
/api/shipping/profile
Simpan / update profil shipping
{
"origin_postal_code": "{{ORIGIN_POSTAL}}",
"origin_city": "{{ORIGIN_CITY}}",
"origin_province": "{{ORIGIN_PROVINCE}}",
"origin_label": "{{ORIGIN_LABEL}}",
"package_weight_grams": {{PACKAGE_WEIGHT}},
"default_item_value": {{DEFAULT_ITEM_VALUE}},
"default_item_name": "{{DEFAULT_ITEM_NAME}}",
"couriers": "{{COURIERS}}",
"cod": {
"enabled": true,
"fee": 0,
"local_city": "{{ORIGIN_CITY}}",
"area_note": "Area dalam kota",
"eta": "1-2 hari kerja"
}
}
| Field | Wajib | Keterangan |
|---|---|---|
origin_postal_code | Ya | Kode pos gudang/toko asal pengiriman |
origin_area_id | Disarankan wajib | Kecamatan asal dari GET /shipping/areas — akurasi sortir Biteship |
origin_district | Tidak | Nama kecamatan asal (bantu resolve area) |
origin_address | Tidak | Alamat lengkap gudang (sync lokasi Biteship) |
origin_city | Disarankan | Kota asal |
origin_province | Disarankan | Provinsi asal |
origin_label | Tidak | Nama gudang/toko, tampil di response quote |
package_weight_grams | Tidak | Berat default paket (gram). Default: 300 |
default_item_value | Tidak | Nilai barang default (Rp). Default: 150000 |
default_item_name | Tidak | Nama barang default untuk perhitungan ongkir. Default: Paket |
couriers | Tidak | Kurir aktif, pisah koma: jne,jnt,sicepat |
cod | Tidak | Opsi COD lokal per kota merchant |
curl -X PUT {{BASE_URL}}/shipping/profile \
-H "Content-Type: application/json" \
-H "Authorization: Bearer SESSION_TOKEN" \
-d '{
"origin_postal_code": "{{ORIGIN_POSTAL}}",
"origin_city": "{{ORIGIN_CITY}}",
"origin_province": "{{ORIGIN_PROVINCE}}",
"package_weight_grams": {{PACKAGE_WEIGHT}},
"default_item_value": {{DEFAULT_ITEM_VALUE}},
"couriers": "{{COURIERS}}"
}'
{
"status": true,
"message": "Profil shipping disimpan.",
"data": {
"origin_postal_code": "{{ORIGIN_POSTAL}}",
"profile_ready": {{PROFILE_READY}},
...
}
}
Setiap cek ongkir dan pembuatan resi memotong saldo wallet FN Shipping merchant. Gunakan endpoint ini dari backend sebelum quote/booking — terutama saat automasi checkout.
/api/shipping/wallet/balance
Cek saldo wallet (server integrasi)
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
atau
Authorization: Bearer <session_token>
{
"status": true,
"data": {
"balance": 85000,
"min_topup": 10000,
"low_balance": false,
"low_threshold": 20000,
"dashboard_url": "https://ship.fncreative.co.id/dashboard"
}
}
| Field | Arti |
|---|---|
balance | Saldo tersedia (Rp) |
low_balance | true jika saldo di bawah low_threshold |
low_threshold | Batas peringatan saldo rendah (default Rp 20.000) |
min_topup | Minimal nominal top-up via dashboard |
dashboard_url | Link top-up saldo (UI dashboard, bukan API) |
curl "{{BASE_URL}}/shipping/wallet/balance" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}"
422 insufficient_balance.
GET /shipping/wallet (detail + riwayat) hanya untuk UI dashboard (Bearer).
/api/shipping/quote
Hitung tarif ekspedisi atau COD lokal
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
Content-Type: application/json
Profil shipping sudah diisi di dashboard. Server otomatis pakai asal kirim, kurir, berat & nilai barang dari profil.
{
"method": "expedition",
"city": "Jakarta Selatan",
"province": "DKI Jakarta",
"postal_code": "12190",
"area_id": "IDNP6IDNC148IDND843IDZ12250",
"receiver_district": "Pesanggrahan"
}
Gunakan jika satu merchant punya beberapa gudang, atau berat/nilai berbeda per produk.
{
"method": "expedition",
"city": "Jakarta Selatan",
"province": "DKI Jakarta",
"postal_code": "12190",
"area_id": "IDNP6IDNC148IDND843IDZ12250",
"site_config": {
"originPostalCode": "{{ORIGIN_POSTAL}}",
"couriers": "{{COURIERS}}",
"packageWeightGrams": {{PACKAGE_WEIGHT}}
},
"item": {
"name": "Jaket Premium",
"value": 450000,
"weight": 800,
"quantity": 1
}
}
| Field | Sumber |
|---|---|
originPostalCode | Request site_config → jika kosong, pakai profil merchant |
couriers | Request → profil |
item.weight | Request item → package_weight_grams profil |
item.value | Request item → default_item_value profil |
item.name | Request item → default_item_name profil |
curl -X POST {{BASE_URL}}/shipping/quote \
-H "Content-Type: application/json" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}" \
-d '{
"method": "expedition",
"city": "Jakarta Selatan",
"province": "DKI Jakarta",
"postal_code": "12190",
"area_id": "IDNP6IDNC148IDND843IDZ12250"
}'
{
"status": true,
"data": {
"ok": true,
"method": "expedition",
"provider": "live",
"origin": "{{ORIGIN_LABEL}}",
"destination": "Jakarta Selatan, DKI Jakarta, 12190",
"weight_grams": {{PACKAGE_WEIGHT}},
"rates": [
{
"id": "fn:jne:reg",
"courier": "JNE",
"service": "Reguler",
"courier_code": "jne",
"service_code": "reg",
"price": 21290,
"eta": "2 - 3 days",
"provider": "live"
}
],
"profile": {
"origin_postal_code": "{{ORIGIN_POSTAL}}",
"origin_label": "{{ORIGIN_LABEL}}",
"package_weight_grams": {{PACKAGE_WEIGHT}},
"default_item_value": {{DEFAULT_ITEM_VALUE}}
}
}
}
rates[].price sebagai ongkir final di checkout — jangan hitung ulang di client.
Simpan rates[].id bersama rates[].price untuk referensi saat buat resi.
Aktifkan di profil merchant (cod.enabled: true) lalu panggil:
{
"method": "cod",
"city": "Kota Surabaya",
"province": "Jawa Timur"
}
COD hanya tersedia jika kota tujuan cocok dengan cod.local_city di profil.
| Field | Wajib | Keterangan |
|---|---|---|
method | Ya | expedition atau cod |
city | Ya* | Kota tujuan (*wajib untuk expedition) |
province | Ya* | Provinsi tujuan |
postal_code | Ya* | Kode pos dari wilayah terpilih (areas) |
area_id / receiver_area_id | Ya* | areas[].id — wajib ekspedisi domestik (v1.10+) |
receiver_district | Disarankan | Kecamatan (label & fallback lookup) |
site_config | Tidak | Override asal/kurir — jika kosong pakai profil |
item | Tidak | Override berat/nilai/nama barang |
destination_country | Tidak | ISO2 tujuan. Default ID (domestik). Ekspor ASEAN: SG, MY, TH, VN, PH, BN, KH, LA, MM, TL. Asal selalu Indonesia. COD tidak tersedia untuk ekspor. |
Belum diaktifkan di production. Jangan mengintegrasikan checkout luar negeri sebelum FN CREATIVE mengumumkan rilis resmi. Dokumentasi field ekspor di halaman ini dapat berubah atau dinonaktifkan di server live.
{
"method": "expedition",
"destination_country": "SG",
"city": "Singapore",
"postal_code": "018956",
"item": { "name": "Jaket Premium", "value": 450000, "weight": 800 }
}
Response rate ekspor/internasional tidak tersedia di production saat ini. Gunakan quote domestik (destination_country: ID atau default).
id (area kecamatan), postal_code,
district, city, province. Simpan id untuk quote & booking —
customer tidak perlu mengetik kode pos manual.
/api/shipping/areas?input=Kediri
Autocomplete wilayah Indonesia sekaligus sumber kode pos tujuan.
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
curl "{{BASE_URL}}/shipping/areas?input=Surabaya" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}"
{
"status": true,
"data": {
"ok": true,
"areas": [
{
"id": "IDNP6IDNC148IDND843IDZ11450",
"name": "Surabaya, Surabaya, Jawa Timur",
"city": "Surabaya",
"province": "Jawa Timur",
"district": "Surabaya",
"postal_code": "60111"
}
]
}
}
Sediakan satu kolom pencarian wilayah (atau dropdown provinsi → kota → kecamatan).
Saat customer memilih salah satu hasil, simpan id (sebagai area_id),
city, province, district, dan postal_code.
Kolom kode pos & area_id cukup hidden atau read-only.
// 1) Customer mengetik nama kelurahan/kecamatan/kota
const res = await fetch(
`{{BASE_URL}}/shipping/areas?input=${encodeURIComponent(keyword)}`,
{ headers: { "x-api-key": process.env.FN_API_KEY, id_merchant: process.env.FN_MERCHANT_ID } }
);
const { data } = await res.json();
// 2) Customer memilih salah satu wilayah dari data.areas
const area = data.areas[selectedIndex];
// 3) Kode pos terisi otomatis — tidak diketik customer
form.receiver_city.value = area.city;
form.receiver_province.value = area.province;
form.receiver_postal.value = area.postal_code; // hidden / read-only
form.receiver_area_id.value = area.id; // hidden — wajib domestik
// 4) Pakai nilai itu untuk hitung ongkir
await fetch("{{BASE_URL}}/shipping/quote", {
method: "POST",
headers: {
"content-type": "application/json",
"x-api-key": process.env.FN_API_KEY,
id_merchant: process.env.FN_MERCHANT_ID,
},
body: JSON.stringify({
method: "expedition",
city: area.city,
province: area.province,
postal_code: area.postal_code,
area_id: area.id,
receiver_district: area.district,
}),
});
x-api-key tidak terekspos ke publik.
postal_code tetap wajib dikirim pada /shipping/quote
dan /shipping/orders — yang otomatis adalah cara mendapatkannya, bukan boleh dikosongkan.
Kelola pesanan pengiriman: buat resi baru, lihat riwayat, detail, lacak, dan unduh label.
Auth: x-api-key + id_merchant atau Authorization: Bearer.
/api/shipping/orders
Daftar riwayat resi merchant (paginated)
| Param | Contoh | Fungsi |
|---|---|---|
limit | 50 | Maks. baris (default 50) |
offset | 0 | Offset pagination |
month | 2026-06 | Filter bulan (YYYY-MM) |
status | shipped | Filter status |
q | JNE | Cari resi, nama, kota, reference_id |
{
"status": true,
"data": {
"orders": [
{
"reference_id": "INV-2026-00042",
"waybill_id": "1408512600009096",
"courier": "JNE",
"service": "reg",
"recipient_name": "Budi Santoso",
"recipient_phone": "081234567890",
"destination_summary": "Budi · Jakarta Selatan · DKI Jakarta · 12190",
"price": 21290,
"status": "shipped",
"created_at": "2026-06-30T10:00:00.000Z"
}
],
"total": 1,
"offset": 0,
"limit": 50,
"has_more": false,
"summary": { "total": 1, "by_status": { "shipped": 1 } }
}
}
curl "{{BASE_URL}}/shipping/orders?limit=20&month=2026-06" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}"
reference_id sendiri —
polling detail/tracking cukup via GET /shipping/orders/:id.
/api/shipping/orders
Buat pesanan pengiriman & resi kurir — panggil setelah pembayaran lunas
Wajib gunakan rate_id dari POST /shipping/quote (format fn:jne:reg).
Cek saldo via GET /shipping/wallet/balance sebelum booking.
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
Content-Type: application/json
{
"reference_id": "INV-2026-00042",
"rate_id": "fn:jne:reg",
"shipping_cost": 21290,
"destination": {
"contact_name": "Budi Santoso",
"contact_phone": "081234567890",
"contact_email": "budi@example.com",
"address": "Jl. Sudirman No. 1, Kebayoran Baru",
"postal_code": "12190",
"city": "Jakarta Selatan",
"province": "DKI Jakarta",
"district": "Pesanggrahan",
"area_id": "IDNP6IDNC148IDND843IDZ12250",
"note": "Patokan: gerbang hijau"
},
"product_name": "Kaos FN CREATIVE",
"size": "L",
"colour": "Black",
"sleeve": "Short",
"order_note": "Pre-order — jangan dibanting",
"collection_method": "pickup",
"items": [
{
"name": "Kaos FN CREATIVE",
"description": "Black · L · Short",
"category": "fashion",
"value": {{DEFAULT_ITEM_VALUE}},
"weight": {{PACKAGE_WEIGHT}},
"quantity": 1,
"length": 10,
"width": 10,
"height": 10
}
]
}
items[] dari database order toko — FN tidak bisa menebak size/warna dari sistem Anda.
Anda bisa mengisi varian lewat items[].description (disarankan, contoh: Black · L · Short)
atau field root product_name, size, colour/color, sleeve
— server FN akan menggabungkannya ke items saat disimpan.
Jika items kosong, hanya nama/berat default profil merchant yang dipakai (tanpa size/warna).
| Field | Wajib | Keterangan |
|---|---|---|
reference_id | Disarankan | ID order di sistem Anda (unik). Jika dikirim ulang dengan ID sama, server mengembalikan resi yang sudah ada (idempoten). |
rate_id | Ya | Dari rates[].id hasil quote. Jangan ubah manual. |
destination.contact_name | Ya | Nama penerima |
destination.contact_phone | Ya | Nomor HP penerima (format Indonesia) |
destination.contact_email | Tidak | Email penerima (opsional) |
destination.address | Ya | Alamat lengkap tujuan |
destination.postal_code | Ya | Kode pos dari areas yang dipilih customer |
destination.area_id | Ya (domestik) | Sama dengan areas[].id saat quote — tanpa ini API mengembalikan destination_area_required |
destination.district | Disarankan | Kecamatan (arsip & fallback) |
destination.city | Tidak | Kota (label & arsip) |
destination.province | Tidak | Provinsi (label & arsip). Untuk ekspor boleh dikosongkan. |
destination.country | Tidak | ISO2 tujuan. Default ID. Untuk ekspor ASEAN isi SG/MY/TH/dll. Bisa juga kirim destination_country di root request. |
quoted_price / shipping_cost | Disarankan | Harga ongkir saat checkout (rates[].price). Dipakai validasi saat booking — jika drift > 8% dari tarif live, sistem menyesuaikan otomatis. |
order_note | Tidak | Catatan internal (disimpan di data order). Tidak dicetak di label fisik resi. |
collection_method | Tidak | pickup (default) atau drop_off |
items[] | Disarankan | Detail barang per paket. Wajib diisi jika toko punya varian (size/warna). Jika kosong, default profil (biasanya hanya "Paket"). |
items[].name | Disarankan | Nama produk / merchandise |
items[].description | Disarankan | Ringkasan varian untuk label & dashboard, contoh: Black · L · Short |
items[].category | Tidak | Kategori (default fashion) |
items[].value, weight, quantity | Disarankan | Nilai barang (Rp), berat (gram), qty |
items[].length, width, height | Tidak | Dimensi cm (opsional, untuk arsip) |
product_name | Tidak | Alternatif: nama produk di root request jika belum pakai items[].name |
size, colour/color, sleeve | Tidak | Alternatif varian di root — digabung ke items saat booking |
Asal pengiriman (kode pos, alamat gudang) diambil otomatis dari profil merchant Anda — tidak perlu dikirim ulang di setiap request.
curl -X POST {{BASE_URL}}/shipping/orders \
-H "Content-Type: application/json" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}" \
-d '{
"reference_id": "INV-2026-00042",
"rate_id": "fn:jne:reg",
"shipping_cost": 21290,
"destination": {
"contact_name": "Budi Santoso",
"contact_phone": "081234567890",
"address": "Jl. Sudirman No. 1",
"postal_code": "12190"
},
"items": [{
"name": "Kaos FN CREATIVE",
"description": "Black · L · Short",
"weight": {{PACKAGE_WEIGHT}},
"value": {{DEFAULT_ITEM_VALUE}},
"quantity": 1
}]
}'
{
"status": true,
"data": {
"id": "INV-2026-00042",
"reference_id": "INV-2026-00042",
"status": "processing",
"courier": "JNE",
"service": "reg",
"rate_id": "fn:jne:reg",
"waybill_id": "",
"tracking_id": "",
"price": 21290,
"origin_postal_code": "{{ORIGIN_POSTAL}}",
"destination": { "contact_name": "Budi Santoso", "postal_code": "12190", "..." : "..." },
"tracking_events": [],
"provider": "live",
"created_at": "2026-06-30T10:00:00.000Z",
"updated_at": "2026-06-30T10:00:00.000Z"
}
}
waybill_id (nomor resi) mungkin masih kosong langsung setelah pembuatan — normal.
Cek lagi via endpoint lacak status beberapa menit kemudian.
price_adjusted, checkout_price, dan debit_price
jika tarif live berbeda dari harga checkout. Gunakan debit_price sebagai ongkir final yang didebit wallet.
/api/shipping/orders/:id
Detail pesanan pengiriman
:id = reference_id yang Anda kirim saat membuat resi (atau id di response).
Hanya order milik merchant Anda yang dapat diakses.
{
"status": true,
"data": {
"id": "INV-2026-00042",
"status": "shipped",
"waybill_id": "JP1234567890",
"tracking_id": "...",
"courier": "JNE",
"..."
}
}
cod). Endpoint /shipping/orders hanya untuk ekspedisi.
/api/shipping/orders/:id/tracking
Status terbaru + riwayat perjalanan paket
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
{
"status": true,
"data": {
"order": {
"id": "INV-2026-00042",
"status": "shipped",
"waybill_id": "JP1234567890",
"courier": "JNE",
"tracking_events": [ "..." ]
},
"tracking": {
"status": "shipped",
"waybill_id": "JP1234567890",
"events": [
{
"status": "shipped",
"note": "Paket telah dijemput kurir",
"location": "Kediri",
"updated_at": "2026-06-30T14:00:00.000Z"
}
]
}
}
}
/api/shipping/orders/:id/cancel
Batalkan pesanan pengiriman (sebelum pickup / sesuai kebijakan kurir)
Memanggil pembatalan di Biteship, mengembalikan saldo wallet merchant jika order sudah didebit, dan men-sync status ke storefront FN.
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
{ "status": true, "data": { "status": "cancelled", "waybill_id": "..." }, "message": "Pesanan pengiriman dibatalkan." }
status)| Status API | Arti untuk customer |
|---|---|
pending | Resi dibuat, menunggu konfirmasi kurir |
processing | Diproses / menunggu pickup |
shipped | Paket dalam perjalanan |
delivered | Sampai tujuan |
cancelled | Dibatalkan |
reference_id saat membuat resi.GET /shipping/orders/:id/tracking setiap 15–30 menit, atau saat customer membuka halaman lacak pesanan.waybill_id sebagai nomor resi ke customer.tracking.events[] sebagai timeline status.GET /shipping/orders/{id}/tracking.
FN CREATIVE menerima webhook Biteship di server (POST /shipping/webhook) dan meneruskan update resi ke storefront terdaftar.
FN Shipping menyediakan label resi bawaan dengan desain standar AWB 100×150 mm: logo kurir resmi, barcode, alamat penerima/pengirim, dan branding FN CREATIVE di footer. Merchant tidak perlu desain label sendiri — cukup panggil endpoint di bawah dari backend.
/api/shipping/orders/:id/label
Label HTML siap cetak di browser
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
| Param | Nilai | Fungsi |
|---|---|---|
print | 1 | Buka dialog cetak otomatis saat halaman dimuat |
Content-Type: text/html — halaman label lengkap dengan barcode (JsBarcode).
curl "{{BASE_URL}}/shipping/orders/INV-2026-00042/label" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}"
/api/shipping/orders/:id/label.pdf
Unduh label sebagai PDF (disarankan untuk admin & thermal printer)
x-api-key: {{API_KEY}}
id_merchant: {{MERCHANT_ID}}
Content-Type: application/pdf — ukuran kertas 100×150 mm, logo kurir embedded, barcode server-side.
Filename: label-resi-{reference_id}.pdf
curl "{{BASE_URL}}/shipping/orders/INV-2026-00042/label.pdf" \
-H "x-api-key: {{API_KEY}}" \
-H "id_merchant: {{MERCHANT_ID}}" \
-o label-resi.pdf
| Bagian | Sumber data |
|---|---|
| Logo kurir + layanan (REG/EZ/dll.) | courier, service dari order |
| Barcode + nomor resi | waybill_id — muncul setelah kurir terbit resi |
| Kode rute kurir | courier_routing_code (contoh 470-JOG04-33) di bawah barcode |
| Alamat penerima | destination saat booking |
| Alamat pengirim | Profil merchant (origin_label, kode pos asal) |
| Grid: Berat · Referensi | Berat paket + reference_id order Anda |
| Grid: Isi paket · Kode rute | Detail item + kode rute kurir |
Footer fncreative.co.id | Teks branding FN (tanpa logo) + badge status bayar |
order_note) — disimpan di API, tidak di label| Query | Fungsi |
|---|---|
?mask=1 | Masking sebagian nama & nomor HP penerima (untuk label customer-facing) |
?print=1 | HTML label auto-trigger cetak |
POST /shipping/orders berhasil.
Jika waybill_id masih kosong, label tetap bisa diunduh — barcode muncul otomatis
setelah resi terbit (panggil ulang endpoint yang sama).
x-api-key, jangan buka URL label langsung di browser customer.
Buat endpoint proxy di backend Anda, contoh: GET /admin/orders/:id/label.pdf
yang meneruskan request ke FN Shipping dengan kredensial server.
// Express — unduh label PDF untuk admin toko
app.get("/admin/shipping/:ref/label.pdf", requireAdmin, async (req, res) => {
const upstream = await fetch(
`${process.env.FN_SHIPPING_API_URL}/shipping/orders/${encodeURIComponent(req.params.ref)}/label.pdf`,
{ headers: {
"x-api-key": process.env.FN_API_KEY,
"id_merchant": process.env.FN_MERCHANT_ID,
}}
);
if (!upstream.ok) return res.status(upstream.status).send(await upstream.text());
res.setHeader("Content-Type", "application/pdf");
res.setHeader("Content-Disposition", upstream.headers.get("content-disposition") || "attachment");
res.send(Buffer.from(await upstream.arrayBuffer()));
});
Setiap opsi kurir di rates[] menyertakan price — ini adalah
ongkir final untuk customer dan wallet merchant. Jangan hitung ulang di sisi toko.
| Field | Arti |
|---|---|
rates[].id | ID kurir/layanan — simpan untuk POST /shipping/orders |
rates[].price | Ongkir final — tampilkan ke customer & simpan sebagai shipping_cost |
rates[].base_price | Biaya kurir sebelum markup (audit/internal) |
rates[].markup_amount | Selisih layanan FN (price − base_price) |
service_fee | Info markup aktif: percent, check_fee per quote |
check_fee_charged | Biaya cek ongkir yang didebit wallet pada request quote ini |
rates[].courier / service | Label kurir untuk UI |
rates[].eta | Estimasi tiba |
| Komponen | Contoh |
|---|---|
| Biaya kurir (dasar) | Rp 7.000 |
| Markup layanan FN (12%) | Rp 840 |
| Biaya cek ongkir (per quote) | Rp 10 |
Harga ke customer (rates[].price) | Rp 7.850 |
POST /shipping/orders), sistem melakukan re-quote live.
Jika harga checkout menyimpang > 8% dari tarif live, wallet didebit tarif live + markup (response price_adjusted: true).
rates[].price persis seperti yang dikembalikan API.
Semua response memakai envelope: sukses {"status":true,"data":{...}}, gagal {"status":false,"message":"..."}.
| HTTP | Code | Penyebab & solusi |
|---|---|---|
| 401 | — |
Kredensial memang ditolak. Periksa x-api-key + id_merchant atau login ulang.
Status ini tidak lagi dipakai untuk gangguan sementara — jika verifikasi akun
tidak bisa dijalankan, yang keluar adalah 503 di bawah.
|
| 403 | merchant_pending_approval |
Akun belum disetujui admin. Tunggu approval sebelum API aktif. |
| 422 | profile_incomplete |
Profil shipping belum diisi. Login ke dashboard → isi origin_postal_code. |
| 422 | insufficient_balance |
Saldo FN Shipping tidak cukup. Top-up di dashboard merchant. |
| 422 | origin_not_configured |
Kode pos asal kosong di profil dan tidak dikirim di request. |
| 404 | not_found |
Pesanan pengiriman tidak ditemukan — periksa reference_id dan pastikan dibuat dengan merchant yang sama. |
| 400 | — | Validasi input gagal (kota/provinsi/kode pos kosong, rate_id invalid, dll.). |
| 429 | — | Terlalu banyak request. Coba lagi nanti. |
| 503 | merchant_auth_unavailable |
Kredensial Anda kemungkinan besar valid, tetapi layanan verifikasi akun sedang dibatasi
atau tidak dapat dihubungi. Tunggu sesuai Retry-After /
retry_after_seconds lalu ulangi request yang sama — jangan mengganti API key
atau memaksa login ulang.
|
| 500 | — | Kesalahan server atau layanan pengiriman sementara tidak tersedia. |
HTTP/1.1 422 Unprocessable Entity
{
"status": false,
"code": "profile_incomplete",
"message": "Profil shipping belum lengkap. Login ke dashboard FN Shipping dan isi kode pos asal pengiriman terlebih dahulu."
}
HTTP/1.1 503 Service Unavailable
Retry-After: 29
{
"status": false,
"code": "merchant_auth_unavailable",
"message": "Verifikasi akun sedang dibatasi (rate limit). Coba lagi sebentar lagi.",
"retry_after_seconds": 29
}
503 sebagai gangguan sementara: ulangi request dengan jeda
(backoff) dan tampilkan pesan netral seperti "cek ongkir sedang sibuk, coba sesaat lagi" ke customer —
bukan pesan minta login.
Alur standar e-commerce / pre-order dengan FN Payment + FN Shipping:
GET /shipping/wallet/balance sebelum quote/bookingGET /shipping/areas → postal_code terisi otomatisPOST /shipping/quote → pilih kurir → simpan rate_id + pricerate.price → QRIS via FN Payment APIPOST /shipping/orderswaybill_id → polling GET /shipping/orders/:id/trackingGET /shipping/orders/:id/label.pdf via backend admin (proxy)// .env server — JANGAN di frontend publik
// FN_SHIPPING_API_URL={{BASE_URL}}
// FN_API_KEY={{API_KEY}}
// FN_MERCHANT_ID={{MERCHANT_ID}}
const FN_SHIPPING = process.env.FN_SHIPPING_API_URL || "{{BASE_URL}}";
const headers = {
"content-type": "application/json",
"x-api-key": process.env.FN_API_KEY,
"id_merchant": process.env.FN_MERCHANT_ID,
};
async function fnShipping(path, { method = "GET", body } = {}) {
const res = await fetch(`${FN_SHIPPING}${path}`, {
method,
headers,
body: body ? JSON.stringify(body) : undefined,
});
const json = await res.json();
if (!res.ok || !json.status) throw new Error(json.message || "FN Shipping error");
return json.data;
}
// Langkah A — cek saldo sebelum operasi shipping
async function ensureWalletBalance(min = 20000) {
const data = await fnShipping("/shipping/wallet/balance");
if ((data.balance || 0) < min) {
throw new Error(`Saldo FN Shipping rendah: Rp ${data.balance}. Top-up di dashboard.`);
}
return data;
}
// Langkah B — saat customer isi alamat checkout
async function getRates(city, province, postalCode) {
const data = await fnShipping("/shipping/quote", {
method: "POST",
body: {
method: "expedition",
city,
province,
postal_code: postalCode,
},
});
return data.rates; // tampilkan ke customer, simpan rates[].id + rates[].price
}
// Langkah C — setelah pembayaran LUNAS (webhook payment atau cek status)
async function createShipment(order) {
return fnShipping("/shipping/orders", {
method: "POST",
body: {
reference_id: order.id,
rate_id: order.shipping_rate_id,
quoted_price: order.shipping_cost,
shipping_cost: order.shipping_cost,
destination: {
contact_name: order.customer_name,
contact_phone: order.customer_phone,
contact_email: order.customer_email,
address: order.shipping_address,
postal_code: order.shipping_postal,
city: order.shipping_city,
province: order.shipping_province,
},
},
});
}
// Langkah D — halaman lacak pesanan customer
async function trackShipment(referenceId) {
const data = await fnShipping(`/shipping/orders/${encodeURIComponent(referenceId)}/tracking`);
return {
resi: data.tracking.waybill_id,
status: data.tracking.status,
timeline: data.tracking.events,
};
}
// Langkah E — unduh label PDF untuk admin/gudang (server-side)
async function downloadLabelPdf(referenceId) {
const res = await fetch(
`${FN_SHIPPING}/shipping/orders/${encodeURIComponent(referenceId)}/label.pdf`,
{ headers: { "x-api-key": process.env.FN_API_KEY, "id_merchant": process.env.FN_MERCHANT_ID } }
);
if (!res.ok) throw new Error("Gagal unduh label PDF");
return Buffer.from(await res.arrayBuffer());
}
| Field | Kapan diisi |
|---|---|
shipping_rate_id | Saat customer pilih kurir (dari quote) |
shipping_cost | rates[].price dari quote — jangan hitung ulang |
shipping_courier / shipping_service | Label dari quote (opsional, untuk tampilan) |
fn_shipping_reference | reference_id saat buat resi |
waybill_id | Dari response tracking setelah resi terbit |
shipping_status | Dari tracking.status (polling) |
x-api-key di environment variable — rotasi jika bocor.shipping_rate_id dengan memanggil quote lagi sebelum terima pembayaran (hindari manipulasi harga ongkir).POST /orders) hanya setelah pembayaran benar-benar lunas.reference_id unik per order — aman untuk retry tanpa duplikasi resi.