JetWize Partner API
Sandbox
Production
v1.0
REST
Integrasikan sistem Anda dengan platform pemesanan tiket penerbangan JetWize. API ini memungkinkan Anda mencari penerbangan, membuat booking, dan memproses pembayaran secara programatik.
Overview
| Base URL (Sandbox) | https://api.jetwize.com/api/v1/partner/v1 |
| Base URL (Production) | https://api.jetwize.com/api/v1/partner/v1 |
| Format | JSON (application/json) |
| Rate Limit | 100 request/menit per API key |
| Timeout | 30 detik |
Endpoint sandbox dan production menggunakan URL yang sama. Perbedaannya ditentukan oleh jenis API key yang digunakan (jwz_test_* atau jwz_live_*).
Environments
๐งช Sandbox (jwz_test_...)
- Menggunakan data penerbangan simulasi (tidak ada tiket nyata)
- Wallet saldo virtual: Rp 999.999.999 (tidak ada debit nyata)
- Booking dibuat tapi tidak diproses ke supplier
- Cocok untuk development dan testing integrasi
Production (jwz_live_...)
Semua transaksi nyata โ menggunakan supplier flight aktif, debit wallet nyata. Pastikan integrasi sudah teruji di sandbox sebelum switch ke production.
Authentication
Semua request ke Partner API memerlukan API key. Gunakan salah satu metode berikut:
Option 1 โ Header X-API-Key (Recommended)
X-API-Key: jwz_test_a1b2c3d4e5f6...
Option 2 โ Authorization Bearer
Authorization: Bearer jwz_test_a1b2c3d4e5f6...
Mendapatkan API Key
API key dikelola melalui dashboard JetWize oleh admin agensi Anda:
- Login ke jetwize.com
- Buka Settings โ API Keys
- Klik Buat API Key Baru
- Pilih environment: Sandbox atau Production
โ ๏ธ Jaga kerahasiaan API key Anda. Jangan expose di frontend/client-side code atau commit ke repository. Gunakan environment variables.
Common Errors
Semua error mengembalikan format JSON yang konsisten:
{
"error": {
"code": "BOOKING_EXPIRED",
"message": "Booking hold telah kadaluarsa",
"details": { "bookingId": "..." },
"traceId": "req-abc123"
}
}
Tabel di bawah ini berisi error yang bisa terjadi di endpoint mana pun (auth, rate-limit, server-side). Untuk error spesifik per-endpoint, lihat bagian Possible Errors di setiap endpoint.
| HTTP Status | Error Code | Keterangan |
| 401 | MISSING_API_KEY | Header X-API-Key tidak ada |
| 401 | INVALID_API_KEY | API key tidak valid atau dinonaktifkan |
| 401 | EXPIRED_API_KEY | API key sudah kadaluarsa |
| 401 | AGENCY_INACTIVE | Agensi tidak aktif / suspended |
| 403 | FORBIDDEN | API key tidak punya hak akses ke resource ini |
| 429 | RATE_LIMIT_EXCEEDED | Terlalu banyak request โ tunggu beberapa saat |
| 500 | INTERNAL_ERROR | Server error, retry dengan exponential backoff |
| 503 | SERVICE_UNAVAILABLE | Service sementara tidak tersedia (maintenance) |
GET /me โ Info API Key
Response
{
"apiKey": {
"id": "clx123...",
"name": "My Integration",
"environment": "SANDBOX"
},
"agency": {
"id": "clx456...",
"name": "PT Maju Travel"
},
"wallet": {
"balance": 999999999,
"holdBalance": 0,
"currency": "IDR"
},
"sandbox": true
}
Flight Search
Cari penerbangan tersedia berdasarkan rute, tanggal, dan jumlah penumpang. Endpoint ini di-aggregate dari semua supplier aktif (JGT, FR24, dll.) dan return list offer yang siap di-booking.
Request Body
| Field | Type | Keterangan |
| origin* | string | Kode IATA bandara asal. Contoh: CGK |
| destination* | string | Kode IATA bandara tujuan. Contoh: DPS |
| departDate* | string | Tanggal berangkat format YYYY-MM-DD |
| returnDate | string | Tanggal kembali (untuk pulang-pergi) |
| passengers* | object | { adult, child, infant } |
| cabinClass | string | ECONOMY / BUSINESS / FIRST. Default: ECONOMY |
Request Example
POST /api/v1/partner/v1/flights/search
X-API-Key: jwz_test_...
{
"origin": "CGK",
"destination": "DPS",
"departDate": "2026-06-15",
"passengers": { "adult": 2, "child": 0, "infant": 0 },
"cabinClass": "ECONOMY"
}
Response
{
"results": [
{
"id": "MOCK-CGK-DPS-2026-06-15-1",
"supplierCode": "MOCK",
"segments": [
{
"flightNumber": "GA405",
"carrier": "GA",
"carrierName": "Garuda Indonesia",
"origin": "CGK",
"destination": "DPS",
"departureTime": "2026-06-15T06:00:00.000Z",
"arrivalTime": "2026-06-15T08:55:00.000Z",
"duration": 115,
"cabinClass": "ECONOMY",
"baggageAllowance": "20kg"
}
],
"stops": 0,
"totalDuration": 115,
"basePrice": 850000,
"tax": 93500,
"totalPrice": 943500,
"currency": "IDR",
"seatsAvailable": 9
}
],
"count": 5,
"sandbox": true
}
Possible Errors โ Flight Search
| HTTP | Error Code | Keterangan |
| 400 | INVALID_QUERY | Origin / destination / departDate format salah atau tidak ada |
| 400 | INVALID_DATE | Tanggal berangkat di masa lalu, atau returnDate < departDate |
| 400 | INVALID_ROUTE | Origin = destination, atau IATA code tidak dikenali |
| 400 | INVALID_PASSENGER_COUNT | Total pax 0, atau infant > adult (rule lap-infant) |
| 404 | NO_FARE_FOUND | Tidak ada fare di rute+tanggal ini dari supplier aktif |
| 429 | SEARCH_RATE_LIMIT | Khusus search: 60 req/min per API key |
| 502 | SUPPLIER_ERROR | Semua supplier down โ coba lagi nanti |
| 503 | SUPPLIER_UNAVAILABLE | Sebagian supplier rate-limited (LTB / quota circuit open) |
Price Check
Verifikasi ulang harga + availability offer sebelum buat booking. Wajib dipanggil antara flights/search dan POST /bookings โ harga supplier bisa berubah dalam hitungan menit. Kalau priceChanged: true, tampilkan harga baru ke user untuk konfirmasi sebelum booking.
Request Body
| Field | Type | Keterangan |
| offerId* | string | ID offer dari hasil /flights/search |
Request Example
POST /api/v1/partner/v1/flights/price-check
X-API-Key: jwz_test_...
{ "offerId": "MOCK-CGK-DPS-2026-06-15-1" }
Response
{
"offerId": "MOCK-CGK-DPS-2026-06-15-1",
"priceChanged": false,
"previousPrice": null,
"currentPrice": 943500,
"currency": "IDR",
"stillAvailable": true
}
Response Fields
| Field | Type | Keterangan |
| priceChanged | boolean | true kalau harga supplier berubah sejak search |
| previousPrice | number / null | Harga lama (kalau berubah) |
| currentPrice | number | Harga terkini, gunakan ini untuk booking |
| stillAvailable | boolean | false = seat habis / offer kadaluarsa, jangan lanjut booking |
Possible Errors โ Price Check
| HTTP | Error Code | Keterangan |
| 400 | INVALID_OFFER_ID | Format offerId tidak valid |
| 404 | OFFER_NOT_FOUND | Offer expired / tidak ada di cache โ re-search dulu |
| 410 | OFFER_EXPIRED | Cache TTL 5 menit terlewati โ re-search dulu |
| 502 | SUPPLIER_ERROR | Supplier verify endpoint gagal โ retry atau re-search |
Bookings
Request Body
| Field | Type | Keterangan |
| offerId* | string | ID offer dari hasil search |
| passengers* | array | Data penumpang (lihat di bawah) |
| contact* | object | { name, phone, email } |
Passenger Object
| Field | Type | Keterangan |
| title* | string | MR / MRS / MS / MSTR |
| firstName* | string | Nama depan (huruf kapital, sesuai paspor) |
| lastName* | string | Nama belakang |
| dateOfBirth* | string | Format YYYY-MM-DD |
| nationality* | string | Kode negara ISO-2. Contoh: ID |
| passportNo | string | Nomor paspor (wajib untuk penerbangan internasional) |
| passportExpiry | string | Tanggal kadaluarsa paspor YYYY-MM-DD |
| type* | string | ADULT / CHILD / INFANT |
Request Example
POST /api/v1/partner/v1/bookings
X-API-Key: jwz_test_...
{
"offerId": "MOCK-CGK-DPS-2026-06-15-1",
"passengers": [
{
"title": "MR",
"firstName": "BUDI",
"lastName": "SANTOSO",
"dateOfBirth": "1990-01-15",
"nationality": "ID",
"passportNo": "A1234567",
"passportExpiry": "2030-01-01",
"type": "ADULT"
}
],
"contact": {
"name": "BUDI SANTOSO",
"phone": "+6281234567890",
"email": "[email protected]"
}
}
Response
{
"id": "clx789...",
"bookingCode": "JWZ-20260615-ABC123",
"status": "PENDING_PAYMENT",
"expiresAt": "2026-06-15T07:30:00.000Z",
"totalAmount": 943500,
"currency": "IDR"
}
Possible Errors โ Create Booking
| HTTP | Error Code | Keterangan |
| 400 | INVALID_PASSENGER_DATA | Field passenger missing/invalid (mis. passportExpiry < depart date) |
| 400 | PASSENGER_COUNT_MISMATCH | Jumlah passengers tidak match dengan yang di offer |
| 400 | PRICE_CHANGED | Harga supplier berubah โ re-run price-check dulu |
| 400 | SINGLE_NAME_NOT_ALLOWED | Maskapai ini tidak menerima penumpang mononym (single-name) |
| 404 | OFFER_NOT_FOUND | offerId expired โ re-search dulu |
| 409 | SEATS_UNAVAILABLE | Kursi habis sejak search |
| 502 | SUPPLIER_PNR_FAILED | Supplier reject PNR creation |
Possible Errors โ Get Booking
| HTTP | Error Code | Keterangan |
| 404 | BOOKING_NOT_FOUND | Booking ID tidak ada / sudah dihapus |
| 403 | BOOKING_ACCESS_DENIED | Booking milik agensi lain |
Query Parameters
| Param | Type | Keterangan |
| status | string | Filter: PENDING_PAYMENT, PAID, ISSUED, CANCELLED, REFUNDED |
| cursor | string | Pagination cursor dari response sebelumnya |
| limit | number | Default 20, max 100 |
Possible Errors โ List Bookings
| HTTP | Error Code | Keterangan |
| 400 | INVALID_CURSOR | Cursor format invalid atau kadaluarsa |
| 400 | INVALID_STATUS_FILTER | Status filter tidak dikenali |
Request Body
| Field | Type | Keterangan |
| gateway* | string | Hanya WALLET di-support untuk partner API |
Possible Errors โ Pay Booking
| HTTP | Error Code | Keterangan |
| 400 | BOOKING_EXPIRED | Hold booking sudah habis (30 menit) โ buat booking baru |
| 400 | INVALID_STATUS | Booking bukan status PENDING_PAYMENT |
| 402 | INSUFFICIENT_BALANCE | Saldo wallet kurang dari totalAmount |
| 409 | ALREADY_PAID | Booking sudah ke-bayar (idempotency) |
| 502 | SUPPLIER_TICKETING_FAILED | Pay ok di wallet tapi supplier issue gagal โ refund otomatis dijalankan |
Possible Errors โ Cancel Booking
| HTTP | Error Code | Keterangan |
| 400 | CANNOT_CANCEL | Booking sudah ISSUED โ pakai refund flow, bukan cancel |
| 409 | ALREADY_CANCELLED | Booking sudah dibatalkan sebelumnya |
| 502 | SUPPLIER_CANCEL_FAILED | Supplier menolak cancel โ hubungi support |
Response
{
"url": "https://storage.jetwize.com/eticket/...?signature=...&expires=...",
"expiresAt": "2026-06-15T12:15:00.000Z"
}
Possible Errors โ Get E-Ticket
| HTTP | Error Code | Keterangan |
| 400 | TICKET_NOT_ISSUED | Booking belum status ISSUED โ pay dulu |
| 404 | TICKET_NOT_FOUND | E-ticket PDF belum di-generate (job queue masih running) |
Wallet
{
"balance": 5000000,
"holdBalance": 943500,
"currency": "IDR",
"sandbox": false
}
Possible Errors โ Wallet
| HTTP | Error Code | Keterangan |
| 404 | WALLET_NOT_FOUND | Agensi belum punya wallet โ hubungi admin |
Booking Flow
1. POST /flights/search โ Dapat daftar offer + offerId
2. POST /flights/price-check โ Verifikasi harga masih valid
3. POST /bookings โ Buat booking (hold 30 menit)
โโ Response: bookingId, expiresAt, totalAmount
4. GET /wallet โ Cek saldo cukup
5. POST /bookings/:id/pay โ Bayar dengan wallet
โโ Jika bayar dari luar โ topup wallet dulu via dashboard
6. GET /bookings/:id โ Poll status (PAID โ ISSUED)
7. GET /bookings/:id/ticket โ Download e-ticket
๐ก Tip: Booking expire dalam 30 menit. Jika expire sebelum dibayar, ulangi dari step 1.
Webhooks
JetWize mengirimkan notifikasi ke URL webhook Anda saat status booking berubah. Daftarkan webhook URL di dashboard Settings โ Webhooks.
Event Types
| Event | Keterangan |
| booking.issued | Tiket berhasil diterbitkan |
| booking.cancelled | Booking dibatalkan |
| booking.failed | Penerbitan tiket gagal |
| booking.refunded | Refund diproses |
Webhook Payload
POST https://your-server.com/webhook
Content-Type: application/json
X-JetWize-Signature: sha256=...
{
"event": "booking.issued",
"bookingId": "clx789...",
"bookingCode": "JWZ-20260615-ABC123",
"status": "ISSUED",
"tickets": ["0123456789"],
"issuedAt": "2026-06-15T07:15:00.000Z"
}
Code Examples
JavaScript / Node.js
const API = 'https://api.jetwize.com/api/v1/partner/v1';
const KEY = process.env.JETWIZE_API_KEY; // jwz_test_... atau jwz_live_...
async function searchFlights(origin, destination, date) {
const res = await fetch(`${API}/flights/search`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': KEY,
},
body: JSON.stringify({
origin, destination,
departDate: date,
passengers: { adult: 1, child: 0, infant: 0 },
cabinClass: 'ECONOMY',
}),
});
if (!res.ok) throw new Error(await res.text());
return res.json();
}
// Full booking flow
async function bookFlight() {
// 1. Search
const { results } = await searchFlights('CGK', 'DPS', '2026-06-15');
const offer = results[0];
// 2. Price check
const priceCheck = await fetch(`${API}/flights/price-check`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': KEY },
body: JSON.stringify({ offerId: offer.id }),
}).then(r => r.json());
if (!priceCheck.stillAvailable) throw new Error('Penerbangan tidak tersedia');
// 3. Create booking
const booking = await fetch(`${API}/bookings`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-API-Key': KEY },
body: JSON.stringify({
offerId: offer.id,
passengers: [{ title: 'MR', firstName: 'BUDI', lastName: 'SANTOSO',
dateOfBirth: '1990-01-15', nationality: 'ID', type: 'ADULT' }],
contact: { name: 'BUDI SANTOSO', phone: '+6281234567890' },
}),
}).then(r => r.json());
// 4. Pay
const payment = await fetch(`${API}/bookings/${booking.id}/pay`, {
method: 'POST',
headers: { 'X-API-Key': KEY },
}).then(r => r.json());
return { booking, payment };
}
Python
import os, requests
API = 'https://api.jetwize.com/api/v1/partner/v1'
HEADERS = {
'Content-Type': 'application/json',
'X-API-Key': os.environ['JETWIZE_API_KEY']
}
def search_flights(origin, destination, depart_date):
r = requests.post(f'{API}/flights/search', json={
'origin': origin, 'destination': destination,
'departDate': depart_date,
'passengers': {'adult': 1, 'child': 0, 'infant': 0},
'cabinClass': 'ECONOMY'
}, headers=HEADERS)
r.raise_for_status()
return r.json()['results']
def create_booking(offer_id, passengers, contact):
r = requests.post(f'{API}/bookings', json={
'offerId': offer_id,
'passengers': passengers,
'contact': contact
}, headers=HEADERS)
r.raise_for_status()
return r.json()
def pay_booking(booking_id):
r = requests.post(f'{API}/bookings/{booking_id}/pay', headers=HEADERS)
r.raise_for_status()
return r.json()
# Usage
offers = search_flights('CGK', 'DPS', '2026-06-15')
booking = create_booking(offers[0]['id'],
[{'title': 'MR', 'firstName': 'BUDI', 'lastName': 'SANTOSO',
'dateOfBirth': '1990-01-15', 'nationality': 'ID', 'type': 'ADULT'}],
{'name': 'BUDI SANTOSO', 'phone': '+6281234567890'}
)
payment = pay_booking(booking['id'])
PHP
<?php
$API = 'https://api.jetwize.com/api/v1/partner/v1';
$KEY = getenv('JETWIZE_API_KEY');
function jetwize_request($method, $path, $body = null) {
global $API, $KEY;
$ch = curl_init("$API$path");
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => $method,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
"X-API-Key: $KEY",
],
CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
CURLOPT_TIMEOUT => 30,
]);
$res = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($code >= 400) throw new Exception("API Error $code: $res");
return json_decode($res, true);
}
// Search
$result = jetwize_request('POST', '/flights/search', [
'origin' => 'CGK', 'destination' => 'DPS',
'departDate' => '2026-06-15',
'passengers' => ['adult' => 1, 'child' => 0, 'infant' => 0],
]);
$offer = $result['results'][0];
// Book & Pay
$booking = jetwize_request('POST', '/bookings', [
'offerId' => $offer['id'],
'passengers' => [['title'=>'MR','firstName'=>'BUDI','lastName'=>'SANTOSO',
'dateOfBirth'=>'1990-01-15','nationality'=>'ID','type'=>'ADULT']],
'contact' => ['name'=>'BUDI SANTOSO','phone'=>'+6281234567890'],
]);
$payment = jetwize_request('POST', "/bookings/{$booking['id']}/pay");
JetWize Partner API v1 ยท jetwize.com ยท
Support: [email protected]