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
FormatJSON (application/json)
Rate Limit100 request/menit per API key
Timeout30 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_...)

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:

  1. Login ke jetwize.com
  2. Buka Settings โ†’ API Keys
  3. Klik Buat API Key Baru
  4. 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 StatusError CodeKeterangan
401MISSING_API_KEYHeader X-API-Key tidak ada
401INVALID_API_KEYAPI key tidak valid atau dinonaktifkan
401EXPIRED_API_KEYAPI key sudah kadaluarsa
401AGENCY_INACTIVEAgensi tidak aktif / suspended
403FORBIDDENAPI key tidak punya hak akses ke resource ini
429RATE_LIMIT_EXCEEDEDTerlalu banyak request โ€” tunggu beberapa saat
500INTERNAL_ERRORServer error, retry dengan exponential backoff
503SERVICE_UNAVAILABLEService sementara tidak tersedia (maintenance)

GET /me โ€” Info API Key

GET /partner/v1/me Info API key, agensi, dan saldo wallet
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
}

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.

POST /partner/v1/flights/search Cari penerbangan tersedia

Request Body

FieldTypeKeterangan
origin*stringKode IATA bandara asal. Contoh: CGK
destination*stringKode IATA bandara tujuan. Contoh: DPS
departDate*stringTanggal berangkat format YYYY-MM-DD
returnDatestringTanggal kembali (untuk pulang-pergi)
passengers*object{ adult, child, infant }
cabinClassstringECONOMY / 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
HTTPError CodeKeterangan
400INVALID_QUERYOrigin / destination / departDate format salah atau tidak ada
400INVALID_DATETanggal berangkat di masa lalu, atau returnDate < departDate
400INVALID_ROUTEOrigin = destination, atau IATA code tidak dikenali
400INVALID_PASSENGER_COUNTTotal pax 0, atau infant > adult (rule lap-infant)
404NO_FARE_FOUNDTidak ada fare di rute+tanggal ini dari supplier aktif
429SEARCH_RATE_LIMITKhusus search: 60 req/min per API key
502SUPPLIER_ERRORSemua supplier down โ€” coba lagi nanti
503SUPPLIER_UNAVAILABLESebagian 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.

POST /partner/v1/flights/price-check Verifikasi harga sebelum booking

Request Body

FieldTypeKeterangan
offerId*stringID 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

FieldTypeKeterangan
priceChangedbooleantrue kalau harga supplier berubah sejak search
previousPricenumber / nullHarga lama (kalau berubah)
currentPricenumberHarga terkini, gunakan ini untuk booking
stillAvailablebooleanfalse = seat habis / offer kadaluarsa, jangan lanjut booking
Possible Errors โ€” Price Check
HTTPError CodeKeterangan
400INVALID_OFFER_IDFormat offerId tidak valid
404OFFER_NOT_FOUNDOffer expired / tidak ada di cache โ€” re-search dulu
410OFFER_EXPIREDCache TTL 5 menit terlewati โ€” re-search dulu
502SUPPLIER_ERRORSupplier verify endpoint gagal โ€” retry atau re-search

Bookings

POST /partner/v1/bookings Buat booking baru (hold 30 menit)

Request Body

FieldTypeKeterangan
offerId*stringID offer dari hasil search
passengers*arrayData penumpang (lihat di bawah)
contact*object{ name, phone, email }

Passenger Object

FieldTypeKeterangan
title*stringMR / MRS / MS / MSTR
firstName*stringNama depan (huruf kapital, sesuai paspor)
lastName*stringNama belakang
dateOfBirth*stringFormat YYYY-MM-DD
nationality*stringKode negara ISO-2. Contoh: ID
passportNostringNomor paspor (wajib untuk penerbangan internasional)
passportExpirystringTanggal kadaluarsa paspor YYYY-MM-DD
type*stringADULT / 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
HTTPError CodeKeterangan
400INVALID_PASSENGER_DATAField passenger missing/invalid (mis. passportExpiry < depart date)
400PASSENGER_COUNT_MISMATCHJumlah passengers tidak match dengan yang di offer
400PRICE_CHANGEDHarga supplier berubah โ€” re-run price-check dulu
400SINGLE_NAME_NOT_ALLOWEDMaskapai ini tidak menerima penumpang mononym (single-name)
404OFFER_NOT_FOUNDofferId expired โ€” re-search dulu
409SEATS_UNAVAILABLEKursi habis sejak search
502SUPPLIER_PNR_FAILEDSupplier reject PNR creation
GET /partner/v1/bookings/:id Detail booking
Possible Errors โ€” Get Booking
HTTPError CodeKeterangan
404BOOKING_NOT_FOUNDBooking ID tidak ada / sudah dihapus
403BOOKING_ACCESS_DENIEDBooking milik agensi lain
GET /partner/v1/bookings List booking agensi (dengan pagination + filter)

Query Parameters

ParamTypeKeterangan
statusstringFilter: PENDING_PAYMENT, PAID, ISSUED, CANCELLED, REFUNDED
cursorstringPagination cursor dari response sebelumnya
limitnumberDefault 20, max 100
Possible Errors โ€” List Bookings
HTTPError CodeKeterangan
400INVALID_CURSORCursor format invalid atau kadaluarsa
400INVALID_STATUS_FILTERStatus filter tidak dikenali
POST /partner/v1/bookings/:id/pay Bayar dengan saldo wallet

Request Body

FieldTypeKeterangan
gateway*stringHanya WALLET di-support untuk partner API
Possible Errors โ€” Pay Booking
HTTPError CodeKeterangan
400BOOKING_EXPIREDHold booking sudah habis (30 menit) โ€” buat booking baru
400INVALID_STATUSBooking bukan status PENDING_PAYMENT
402INSUFFICIENT_BALANCESaldo wallet kurang dari totalAmount
409ALREADY_PAIDBooking sudah ke-bayar (idempotency)
502SUPPLIER_TICKETING_FAILEDPay ok di wallet tapi supplier issue gagal โ€” refund otomatis dijalankan
POST /partner/v1/bookings/:id/cancel Batalkan booking (sebelum tiket terbit)
Possible Errors โ€” Cancel Booking
HTTPError CodeKeterangan
400CANNOT_CANCELBooking sudah ISSUED โ€” pakai refund flow, bukan cancel
409ALREADY_CANCELLEDBooking sudah dibatalkan sebelumnya
502SUPPLIER_CANCEL_FAILEDSupplier menolak cancel โ€” hubungi support
GET /partner/v1/bookings/:id/ticket URL download e-ticket (signed, berlaku 15 menit)
Response
{
  "url": "https://storage.jetwize.com/eticket/...?signature=...&expires=...",
  "expiresAt": "2026-06-15T12:15:00.000Z"
}
Possible Errors โ€” Get E-Ticket
HTTPError CodeKeterangan
400TICKET_NOT_ISSUEDBooking belum status ISSUED โ€” pay dulu
404TICKET_NOT_FOUNDE-ticket PDF belum di-generate (job queue masih running)

Wallet

GET /partner/v1/wallet Saldo wallet agensi
{
  "balance": 5000000,
  "holdBalance": 943500,
  "currency": "IDR",
  "sandbox": false
}
Possible Errors โ€” Wallet
HTTPError CodeKeterangan
404WALLET_NOT_FOUNDAgensi 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

EventKeterangan
booking.issuedTiket berhasil diterbitkan
booking.cancelledBooking dibatalkan
booking.failedPenerbitan tiket gagal
booking.refundedRefund 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]