Overview
The API returns the same live flight results that power TicketDrafts. You send a route and a date; you get back up to 15 flight options, cheapest first, each with its legs, layovers and an indicative fare. How you display them, and what your website does next, is entirely up to you.
- One-way search per call, economy, one adult. Return and multi-city trips: one call per leg.
- Airports are 3-letter IATA codes (
DAC,DXB,LHR…). - Times are local to each airport. Fares are in USD and indicative.
- Searching never books or holds a seat.
Authentication
Send your key in a header on every request:
X-API-Key: tdk_live_xxxxxxxxxxxxxxxxAuthorization: Bearer <key> is also accepted.
Search flights
/search?from=DXB&to=LHR&date=2026-11-15| Parameter | Required | Description |
|---|---|---|
from | Yes | Departure airport, 3-letter IATA code |
to | Yes | Arrival airport, 3-letter IATA code |
date | Yes | Departure date, YYYY-MM-DD, today or later |
Example request
curl "https://ticketdrafts.xyz/api/v1/search?from=DXB&to=LHR&date=2026-11-15" \
-H "X-API-Key: tdk_live_xxxxxxxxxxxxxxxx"Example response
{
"ok": true,
"search": { "from": "DXB", "to": "LHR", "date": "2026-11-15" },
"count": 1,
"cached": false,
"flights": [
{
"airline": { "code": "EK", "name": "Emirates", "logo": "https://.../EK.png" },
"flight_number": "EK 3",
"departure": { "date": "2026-11-15", "time": "07:45" },
"arrival": { "date": "2026-11-15", "time": "12:05" },
"duration": "8h 20m",
"duration_minutes": 500,
"stops": 0,
"transit": "",
"price": { "amount": 612, "currency": "USD", "estimated": false },
"segments": [
{
"airline": { "code": "EK", "name": "Emirates" },
"flight_number": "EK 3",
"from": { "code": "DXB", "name": "Dubai International Airport" },
"to": { "code": "LHR", "name": "Heathrow Airport" },
"departure": { "date": "2026-11-15", "time": "07:45" },
"arrival": { "date": "2026-11-15", "time": "12:05" },
"duration": "8h 20m",
"duration_minutes": 500,
"aircraft": "AIRBUS A380"
}
],
"layovers": []
}
],
"usage": { "used": 12, "limit": 1000, "remaining": 988, "resets_on": "2026-11-01" }
}Response fields
| Field | Meaning |
|---|---|
departure / arrival | Local date and time at each airport. Arrival can be the next day. |
stops | Number of connections. 0 = nonstop. |
segments | One entry per flight leg, with airline, flight number, airports, times, duration and aircraft. |
layovers | Connection airport and waiting time in minutes, between consecutive segments. |
price.amount | Lowest economy fare seen for one adult, USD. Indicative — show it as a guide price. |
price.estimated | true when the source gave no fare and the figure is our estimate. |
cached | true when the result came from a search made in the last 24 hours. |
usage | Your searches used this month, limit, remaining, and reset date. |
Hotel search
/hotels?city=Dubai&country=United Arab Emirates&check_in=2026-11-15&check_out=2026-11-20| Parameter | Required | Description |
|---|---|---|
city | Yes | City name in English, e.g. Dubai |
country | Recommended | Country name, so the right city is found |
check_in / check_out | Yes | YYYY-MM-DD; check-out after check-in |
Returns up to about 15 real hotels, cheapest first, with stars, guest rating, photos, amenities and the lowest nightly rate in USD. Searching never books a room.
{
"ok": true,
"search": { "city": "Dubai", "country": "United Arab Emirates", "check_in": "2026-11-15", "check_out": "2026-11-20" },
"nights": 5,
"count": 12,
"hotels": [
{
"id": "ChkI7Z6...",
"name": "Rove Downtown",
"stars": 3,
"rating": 4.4, "reviews": 9120,
"room_type": "Standard Room",
"price": { "per_night": 95, "currency": "USD" },
"images": ["https://..."],
"amenities": ["Free Wi-Fi", "Pool"],
"address": "312 Al Sa'ada Street, Dubai",
"location": { "lat": 25.1972, "lon": 55.2744 },
"check_in_time": "3:00 PM", "check_out_time": "12:00 PM"
}
],
"usage": { "used": 13, "limit": 1000, "remaining": 987, "resets_on": "2026-11-01" }
}Details of the hotel your client picked
/hotel?id=ChkI7Z6...&check_in=2026-11-15&check_out=2026-11-20Free, not countedAdds the hotel's address, phone number, check-in and check-out times and photos stored on TicketDrafts (Google's own photo links expire). Use id values from /hotels. It returns only what it adds, so merge it into the hotel object from /hotels (which keeps the amenities, rating and the nightly price for your dates).
{ "ok": true, "hotel": { "id": "ChkI7Z6...", "name": "Rove Downtown", "address": "...", "phone": "+971 4 561 9000", "images": ["https://ticketdrafts.xyz/uploads/hotels/..."], ... } }Check usage
/usageFree, not counted{
"ok": true,
"client": "Your Agency",
"plan": "Business",
"subscription_ends": "2026-11-29",
"rate_limit_per_minute": 30,
"usage": { "used": 12, "limit": 1000, "remaining": 988, "resets_on": "2026-11-01" }
}What counts as a search
- A
/searchcall that returns flights, or a/hotelscall that returns hotels = 1 search from your monthly quota. /hotel(details of one hotel) and/usageare free.- Not counted: errors, invalid input, "no flights found" answers, and the same route and date repeated within 15 minutes.
- The quota resets on the 1st of every month. Unused searches do not carry over.
Errors
Errors return "ok": false with a code and a readable message:
{ "ok": false, "error": { "code": "quota_exceeded", "message": "This month's 1000 searches are used up..." } }| HTTP | Code | What to do |
|---|---|---|
| 400 | bad_airport, bad_date, past_date | Fix the parameters. |
| 401 | missing_key, invalid_key | Header missing or key wrong. |
| 402 | quota_exceeded | Monthly searches used up — upgrade, or wait for the reset. |
| 402 | subscription_expired | Renew your subscription. |
| 403 | key_disabled, ip_not_allowed | Contact us. |
| 429 | rate_limited | Too many calls per minute — retry after 60 seconds. |
| 503 | unavailable | Flight data source busy — retry in a few minutes. Not counted. |
No flights on a route and date returns 200 with "count": 0 and an empty flights list — show "no flights for this date" to your user.
Code examples
PHP
<?php
$url = 'https://ticketdrafts.xyz/api/v1/search?' . http_build_query([
'from' => 'DXB', 'to' => 'LHR', 'date' => '2026-11-15',
]);
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 40,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('TICKETDRAFTS_API_KEY')],
]);
$data = json_decode(curl_exec($ch), true);
curl_close($ch);
if (!empty($data['ok'])) {
foreach ($data['flights'] as $f) {
echo $f['airline']['name'], ' ', $f['flight_number'], ' ',
$f['departure']['time'], ' → ', $f['arrival']['time'], ' ',
$f['price']['amount'], ' USD', PHP_EOL;
}
} else {
echo 'Error: ', $data['error']['message'] ?? 'unknown';
}Node.js (server side)
const params = new URLSearchParams({ from: 'DXB', to: 'LHR', date: '2026-11-15' });
const res = await fetch('https://ticketdrafts.xyz/api/v1/search?' + params, {
headers: { 'X-API-Key': process.env.TICKETDRAFTS_API_KEY },
});
const data = await res.json();
if (data.ok) console.log(data.count, 'flights', data.flights);
else console.error(data.error.code, data.error.message);Ticket layout
Partners with the ticket layout get one script file. It turns a flight your client picked into a flight itinerary in the TicketDrafts format, with your agency's logo, name, address and phone, and downloads it as an A4 PDF. Everything happens in your client's browser: no extra API call, nothing is sent to TicketDrafts, and searches are not counted.
1. Add the script
<script src="https://ticketdrafts.xyz/sdk/c/YOUR-FILE-ID.js"></script>2. Build the booking from the search result
Pass the flight objects exactly as /search returned them: one per direction (outbound, then return).
const booking = {
flights: [outboundFlight, returnFlight], // objects from /search, unchanged
passengers: [
{ title: 'MR', first: 'Rahim', last: 'Uddin', type: 'ADULT',
passport: 'A01234567', nationality: 'Bangladeshi' }
],
pnr: 'K7QX2M', // optional: your own reference (5-8 letters/digits)
cabin: 'Economy', // optional
status: 'Confirmed', // Confirmed | Booked | Reserved | Hold
agency: { // optional: your branding in the ticket header
name: 'Your Agency Name',
logo: '/images/logo.png', // same-site URL or a data: URI
address: 'Office address',
phone: '+971 4 000 0000'
},
fare: { currency: 'AED', rate: 3.6725 } // or { currency: 'AED', adult: 2140 }, or false to hide fares
};3. Show it and download it
// Preview on your page
TDTicket.render('#ticket-preview', booking);
// Download the PDF
document.querySelector('#download').addEventListener('click', () => {
TDTicket.download(booking).catch(err => alert(err.message));
});
// Or get the PDF as a Blob, e.g. to save it on your server or email it
TDTicket.pdf(booking).then(({ blob, pnr, filename }) => { /* upload blob */ });| Field | Notes |
|---|---|
passengers | At least one. type is ADULT, CHILD or INFANT and sets the fare rows. |
pnr | If left out, one is generated and kept on the booking object, so the preview and the PDF match. |
fare | Default: the API fare in USD. rate converts it to your currency; adult sets the adult total yourself. Base fare and taxes are split the same way as on TicketDrafts. |
agency.logo | Load it from your own site (or a data: URI). Images on other sites only work if that site allows cross-origin use. |
Hotel voucher
The same script makes a TicketDrafts hotel voucher. Pass the hotel object (from /hotel, with the price from /hotels):
const voucher = {
hotel: Object.assign({}, hotelFromSearch, hotelDetails.hotel, { price: hotelFromSearch.price }),
check_in: '2026-11-15', check_out: '2026-11-20',
rooms: 1, adults: 2, children: 0,
room_type: 'Deluxe King Room', // optional
guests: [{ title: 'MR', first: 'Rahim', last: 'Uddin', passport: 'A01234567', nationality: 'Bangladeshi' }],
reference: 'HT-K7QX2M', // optional: your own voucher reference
agency: { name: 'Your Agency Name', logo: '/images/logo.png', website: 'youragency.com', email: '[email protected]' },
fare: { currency: 'AED', rate: 3.6725 } // or false to hide prices
};
TDTicket.renderHotel('#voucher-preview', voucher);
TDTicket.downloadHotel(voucher);
TDTicket.hotelPdf(voucher).then(({ blob, ref, filename }) => { /* upload blob */ });The voucher shows the room charge for rooms × nights plus 10% taxes and fees, and your agency's name and contact in the footer.
Good practice
- Allow up to 40 seconds for a fresh (uncached) search.
- Save the flight your customer selected in your own database — search results are not stored for later lookup.
- Keep your key in a server environment variable, not in shared code. If it leaks, ask us for a new one; the old key stops working immediately.
- Optionally send us your server's IP address and we will lock the key to it.
Ready to connect your website?
Questions or a new key: [email protected]