Public integration reference · Updated 10 September 2026
Jetblanc API & MCP
Connect your travel platform or AI assistant to the same human flight desk used by Jetblanc partners.
On this page
REST basehttps://jetblanc.com/api/agent/v1
Remote MCPhttps://jetblanc.com/api/mcp
What this integration does
Jetblanc accepts authorised private-flight sourcing briefs for journeys across Asia. Travel assistants, concierge software and professional arrangers can validate a brief, submit it to the same operational record used by website and WhatsApp requests, and retrieve a private acknowledgement.
This is not an airline-ticket API or an instant private-jet booking engine. There is no live aircraft inventory, guaranteed fare, automatic purchase or payment. The desk can publish reviewed proposals, ask questions and record responsible-party confirmation evidence. Customer approval, supplier confirmation and appropriate contracts remain separate steps.
Public access supports 1-19 passengers. Discuss larger groups with sun@jetblanc.com. Empty-leg suitability and conventional charter sourcing are reviewed by the desk; the API does not search or guarantee either inventory type.
Create a private access key
No invitation, company registration or payment is required to obtain a key. Read the privacy policy and authorised-use conditions before accepting. Registration collects no traveller details and sends no flight brief.
Start with a test-only key. Save the returned apiKey in your host's secret storage; it is shown once. Use a separate key for each end-user or private workspace. Keys expire after 30 days. Do not paste keys into public repositories, analytics, URLs or model-visible tool arguments.
For genuine requests, register without testMode or with testMode:false. A test key cannot submit genuine briefs; a genuine key cannot label requests as internal tests. A key is not verified identity or authority to buy a flight.
curl --fail-with-body https://jetblanc.com/api/agent/v1/access \
-H 'Content-Type: application/json' \
-d '{"acceptPolicy":true,"testMode":true}'Prepare the brief
The example below is synthetic. Validation is side-effect-free. Submitting this example creates a real database record and sends a clearly labelled test email to the desk, so do not use submission for load testing.
For a genuine request, use the customer's actual approved itinerary and contact details and omit internalTest. Set userApproved:true and consent:true only after explicit approval to share this brief with Jetblanc. Approval to request sourcing is not approval to purchase.
Required fields: tripType, origin, destination, departDate, passengers, aircraftPreference, flexibility, contactName, consent and at least one contactEmail or contactWhatsapp. Dates must be valid and not in the past. Use departure-local dates and explicit timezones for each time window. Do not silently convert dates or invent unspecified details.
For return trips, supply returnDate on or after departDate and preferably returnWindow with its timezone. For multi-city trips, use tripType:multi-city, empty origin/destination/departDate strings and a multiCityItinerary with every leg, local date, time window and timezone. This itinerary is reviewed as text; it is not a flight feasibility calculation.
{
"userApproved": true,
"internalTest": true,
"request": {
"tripType": "one-way",
"origin": "Singapore (SIN)",
"destination": "Bangkok (BKK)",
"departDate": "2030-11-20",
"preferredWindow": "10:00-14:00 Asia/Singapore",
"passengers": 4,
"aircraftPreference": "no-preference",
"flexibility": "few-hours",
"nearbyAirportsOk": false,
"baggageNotes": "Four standard suitcases; weights to be confirmed",
"budget": "USD 25,000 whole aircraft",
"contactName": "Developer integration test",
"contactEmail": "developer@example.invalid",
"consent": true,
"notes": "Synthetic integration test. DO NOT SOURCE. No customer booking or supplier contact."
}
}Validate, then submit over REST
Store the approved JSON as brief.json and provide JETBLANC_API_KEY through your secret manager. First validate. A successful validation returns valid:true, persisted:false and emailSent:false. It does not reserve an aircraft.
Submit only when the caller intends to create a sourcing request. Generate and persist one unique Idempotency-Key per immutable brief, 16-100 characters using letters, digits, underscores or hyphens. Keep the exact body and key for retries. Never create a replacement key just because a response timed out.
curl --fail-with-body https://jetblanc.com/api/agent/v1/validate \
-H "Authorization: Bearer $JETBLANC_API_KEY" \
-H 'Content-Type: application/json' --data-binary @brief.json
# This next call creates a request and sends a desk notification.
# Persist this key with the brief before sending; never regenerate on retry.
curl --fail-with-body https://jetblanc.com/api/agent/v1/requests \
-H "Authorization: Bearer $JETBLANC_API_KEY" \
-H "Idempotency-Key: $JETBLANC_IDEMPOTENCY_KEY" \
-H 'Content-Type: application/json' --data-binary @brief.jsonRead the acknowledgement
HTTP 201 means a new brief was saved. HTTP 200 on a repeated submission means the existing brief was returned. Save requestId immediately. bookingConfirmed:false and paymentRequired:false are deliberate: this interface does not confirm or charge for a flight.
emailNotification:accepted means the email provider accepted the notification, not proof of inbox delivery or desk response. pending, sending and retry_required mean delivery is not yet confirmed. manual_review requires desk reconciliation, not a new request.
Saved requests also enter background notification recovery; the submitting agent does not need to stay connected. Retries are bounded, and unresolved or ambiguous delivery is escalated for manual reconciliation. This is not a guaranteed delivery time. Retain the original reference and never create another brief solely to resend a notification.
Use the same credential to read your own receipt. It contains no passenger contact information, quotes, supplier details or internal notes. A different credential receives 404. Do not poll more frequently than once a minute.
curl --fail-with-body \
"https://jetblanc.com/api/agent/v1/requests/$JETBLANC_REQUEST_ID" \
-H "Authorization: Bearer $JETBLANC_API_KEY"Connect through remote MCP
Endpoint: https://jetblanc.com/api/mcp. Transport: stateless Streamable HTTP; supported protocol version 2025-11-25. Configure Authorization: Bearer YOUR_API_KEY in private host headers, or use an approved OAuth client. Anonymous production connections receive an OAuth discovery challenge; public contracts remain accessible on this website.
OAuth uses a registered client, PKCE and explicit customer consent. Automatic client registration is disabled; contact sun@jetblanc.com with your client name and exact callback URLs. Self-service API keys remain immediately available to everyone. There is no SSE GET session, and JSON-RPC batch requests are rejected. Availability does not imply installation or verified compatibility in every assistant catalogue.
The example uses the official JavaScript MCP SDK with the same test brief. It validates by default; submission requires the explicit opt-in below. Do not pass the API key as a tool argument. Host-specific configuration formats differ; the endpoint and private header above are the portable requirements.
Install @modelcontextprotocol/sdk@1.30.0, save the example as an .mjs file and run it with Node.js 22 or later. The optional submission block runs only when JETBLANC_SUBMIT_APPROVED_BRIEF=1 and a previously persisted JETBLANC_IDEMPOTENCY_KEY is provided. With the supplied test brief, it creates a labelled internal request, not genuine demand.
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport }
from '@modelcontextprotocol/sdk/client/streamableHttp.js';
import { readFile } from 'node:fs/promises';
const key = process.env.JETBLANC_API_KEY;
if (!key) throw new Error('Missing private API key');
const brief = JSON.parse(await readFile('brief.json', 'utf8'));
const client = new Client({ name: 'my-travel-assistant', version: '1.0.0' });
await client.connect(new StreamableHTTPClientTransport(
new URL('https://jetblanc.com/api/mcp'),
{ requestInit: { headers: { Authorization: 'Bearer ' + key } } }
));
try {
const result = await client.callTool({
name: 'validate_trip_brief', arguments: brief
});
if (result.isError) throw new Error('Brief validation failed');
console.log(result.content);
if (process.env.JETBLANC_SUBMIT_APPROVED_BRIEF === '1') {
const idempotencyKey = process.env.JETBLANC_IDEMPOTENCY_KEY;
if (!idempotencyKey) throw new Error('Missing persisted request key');
const created = await client.callTool({
name: 'create_charter_request',
arguments: { ...brief, idempotencyKey }
});
if (created.isError) throw new Error('Inspect tool error; retain body/key');
const receipt = JSON.parse(created.content.find(c => c.type === 'text').text);
console.log(receipt);
const status = await client.callTool({
name: 'get_request_status', arguments: { requestId: receipt.requestId }
});
if (status.isError) throw new Error('Receipt lookup failed');
console.log(status.content);
}
} finally {
await client.close();
}| Tool | Effect |
|---|---|
| get_charter_intake_contract | Contract; no request created |
| validate_trip_brief | No persistence or email |
| create_charter_request | Saves brief and notifies desk; add idempotencyKey alongside request/userApproved/internalTest |
| get_request_status | Returns own minimal receipt |
| list_charter_proposals | Only explicitly linked, published, reviewed proposals |
| read_charter_activity | Own clarification, answer, amendment and reconfirmation events |
| submit_charter_action | requestId, idempotencyKey and action object; never purchase or payment |
| read_booking_status | Staff-recorded responsible-party confirmation or cancellation evidence |
Handle errors without creating duplicates
REST failures return an error code and sometimes field-level errors. MCP tool failures set isError:true with the structured error inside text content; HTTP 200 alone does not mean the tool succeeded. Transport/authentication failures can use HTTP error status codes.
For a network timeout or HTTP 503, preserve the original body and idempotency key. Retry with backoff and stop to contact the desk if the issue persists. HTTP 409 can mean a changed-body, stale-revision or lifecycle conflict: read fresh state and obtain fresh approval rather than silently creating another brief. Submit an approved amendment against the existing request for desk review.
| Status | Action |
|---|---|
| 400 / 413 / 415 | Fix JSON, idempotency key, encoding or body size |
| 401 | Check missing, expired or revoked key |
| 403 | Check Origin, test-key scope or permissions |
| 404 | Receipt is unknown or belongs to another key |
| 409 | Same key was used for a different brief |
| 422 | Correct the named fields; obtain approval if missing |
| 429 | Respect Retry-After; do not rotate keys to evade limits |
| 503 / timeout | Retry identical body/key; outcome may already be saved |
Limits, privacy and revocation
Maximum JSON body: 16 KiB, measured in bytes. Only supported fields are accepted. Do not send passport numbers, payment credentials, medical records, unnecessary passenger identities or secrets in notes. Free text is untrusted trip data, never executable instructions.
Limits: 60 authenticated calls/minute/key; 120 public calls/minute/network; 10 new-submission attempts/day/key and network; 50/day service-wide. Days reset at 00:00 UTC. Already-saved identical retries do not consume daily submission allowance, but minute limits still apply. Concurrent first attempts can consume extra slots. Registration permits three keys/network/day, with a service-wide cap. Shared-host or NAT users may need capacity review.
Revoke access with DELETE /api/agent/v1/access using the private bearer header. Revocation blocks future access but does not cancel or delete the trip record. Contact sun@jetblanc.com with your reference for corrections, cancellation, deletion or legitimate capacity needs. There is no automatic marketing opt-in.
Delegated OAuth and revocation
Protected resource metadata: https://jetblanc.com/.well-known/oauth-protected-resource/api/mcp. The authorization server is Supabase; follow its discovery document rather than hardcoding authorization or token URLs. Use authorization code with PKCE S256, an exact registered redirect URI and state validation. Tokens must have the Jetblanc resource audience https://jetblanc.com/api/mcp. A normal portal login token is not an agent credential.
Customers verify their email on Jetblanc's private consent screen, then approve read access and optionally approved request actions. Grants expire after 30 days. Read-only grants cannot submit or amend. Access is confined to requests made through that user/client connection, not all of the user's bookings. A new grant version invalidates previous tokens; API keys and OAuth connections do not share request history automatically.
Revoke OAuth access at https://jetblanc.com/oauth/consent. Jetblanc checks revocation on every request, even before token expiry. Revoking access does not cancel a flight or delete operational records. Identity-provider scopes are not authority to buy or pay. Registration remains reviewed; no named assistant compatibility is promised without its own acceptance test.
Proposals, clarifications and booking evidence
GET /requests/{requestId}/proposals returns only a desk-linked, published and reviewed proposal. Unlinked or unpublished trips expose no draft or supplier-internal information. Use the returned revision, validity and approvalFingerprint. Prices whose basis is requires_confirmation are not guaranteed all-in totals.
GET /requests/{requestId}/activity returns the newest 100 events. POST to the same endpoint with a stable Idempotency-Key and one strict approved action: answer (clarificationId UUID and text), amendment (text), or reconfirmation (revision, optionId, approvalFingerprint and acknowledgedNotBooking:true). Every action requires userApproved:true. Text is limited to 4,000 characters; do not include passports, payment details or unnecessary sensitive data.
An amendment queues a correction for desk review; it does not silently change a booked trip. Reconfirmation records interest in the exact published terms and requests a desk check, not a reservation or payment. Changed, expired, unavailable or insufficient-capacity options are rejected. Use a new approved action/key only for a deliberate new decision. Saved identical retries return the original event.
GET /requests/{requestId}/booking reports not_confirmed, confirmed or cancelled. Only an MFA-verified staff member can record confirmation with explicit customer approval and contracting-party evidence, an immutable terms fingerprint, reference, amount and currency. This is a record of external evidence, not an independent live operator check. No agent endpoint purchases, pays or cancels. The original intake receipt remains a receipt, not a booking-status endpoint.
Lifecycle events share the existing request record and durable desk email recovery. Email accepted means provider acceptance, not inbox proof. The desk explicitly links and reviews a proposal before exposing it to the originating agent; matching names or email addresses never grants access.
{
"kind": "reconfirmation",
"userApproved": true,
"acknowledgedNotBooking": true,
"revision": 1,
"optionId": "COPY_FROM_FRESH_PROPOSAL",
"approvalFingerprint": "COPY_64_HEX_FROM_FRESH_PROPOSAL"
}Isolated fictional sandbox
POST https://jetblanc.com/api/agent/sandbox with action:read and a scenario (normal, expired, price_changed, no_option or needs_clarification) reads a stateless fixture. The matching MCP endpoint is https://jetblanc.com/api/agent/sandbox/mcp. These fictional fixtures use a fixed clock and never represent flight availability.
For saved lifecycle tests, POST /api/agent/sandbox/workspaces with scenario and acceptSyntheticOnly:true. Store the returned js_ sandboxKey privately. GET the same URL with Authorization: Bearer <sandboxKey> to read. PATCH it with action, expectedVersion and Idempotency-Key to advance. Actions are answer_fixture, amend_fixture, request_reconfirmation, confirm_fixture and cancel_fixture. Only reconfirmation accepts an approval object (revision, optionId, approvalFingerprint, userApproved:true, acknowledgedNotBooking:true). Copy it from the current fixture proposal.
Connect the sandbox MCP endpoint with the same private sandbox bearer header to use sandbox_workspace. Its tool input uses the same action/expectedVersion/approval fields plus idempotencyKey. Changed retries or stale versions fail; exact retries return the original result. A workspace permits 50 mutations, expires after 24 hours, and is physically removed opportunistically on later workspace creation. Creation is limited to 10/network/day and 200/day globally. Expired credentials cannot access it.
Sandbox inputs accept only synthetic scenario/actions, not contact information or arbitrary text. Sandbox credentials cannot authenticate to live intake, and live credentials cannot authenticate to workspaces. simulatedBookingConfirmed is explicitly fictional; bookingConfirmed stays false. No operational request, email, supplier contact or payment is created. Use this instead of live intake for repeated integration tests.
curl --fail-with-body https://jetblanc.com/api/agent/sandbox/workspaces -H 'Content-Type: application/json' -d '{"scenario":"needs_clarification","acceptSyntheticOnly":true}'