GGT WORLDWISE Booking API
Create job requests from your own system · v1 · revised October 19, 2026
Overview
One call creates a job request in GGT WORLDWISE and gives you back a
ggt_id — the booking reference that appears on every
confirmation, notice, brief and invoice for that trip. Quote it to us and we both mean the
same piece of work.
Eight fields are required: who the job is for, what service you want, where it starts and ends, what kind of place each end is, and when. Everything else is optional and can be filled in by a GGT operator afterwards.
A job created here lands on Needs Quote/Confirm. An operator reviews it before anything reaches a supplier. Nothing is dispatched, and no email goes to a traveller or a vendor, on the strength of an API call alone — so a bad call is a tidy-up, never a car at the wrong airport.
Getting access
- Register at api.ggtinternal.com and confirm your email.
- A test key is issued straight away. It authenticates and reads, but will not create a job.
- GGT links your account to the customer accounts you book for. This is a person's decision, not an automatic one.
- Once linked, create a live key in the portal. That key creates real jobs.
cid identifies one
of GGT's customers, and a booking against it becomes that customer's bill. Confirming an
email address proves you can read a mailbox; it does not prove you are allowed to spend
somebody's money. Linking is the step that does.Authentication
Send your key in an X-API-Key header on every request.
Authorization: Bearer <key> works too, if your HTTP client
prefers it.
X-API-Key: ggt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Server side only. Never in browser JavaScript, a mobile app, or a repository. We send no CORS headers, so a browser cannot call this API directly — that is deliberate.
- Outside the web root. An environment variable, or a config file that is not served.
- Never in a URL. Query strings end up in access logs and browser history. The key goes in a header, and only in a header.
- One key per system, so revoking one does not take the others down with it.
- If a key might have leaked, revoke it. It stops working at once, and you can issue a replacement in the same breath.
Conventions
- HTTPS only. Plain HTTP is redirected; do not follow the redirect with a key attached.
- JSON in, JSON out. Send
Content-Type: application/jsonon a POST. - Dates are
YYYY-MM-DD. Times areHH:MM, 24-hour, local to the pick-up — not UTC. A car arrives at a kerb at a local time, and that is the only time anybody involved uses. - Every reply carries
okandrequest_id. Quote therequest_idwhen you ask us about a call. - Unknown fields in a request body are ignored, so we can add fields without breaking you.
Create a job
The smallest call that works — the eight required fields:
curl -X POST https://api.ggtinternal.com/v1/jobs \
-H "X-API-Key: ggt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"cid": 1054,
"serviceTypeId": 1,
"pickup": "Fairmont Monte Carlo, 12 Avenue des Spelugues, 98000 Monaco",
"pickup_type": "address",
"dropoff": "LFMN/NCE - Nice Cote d Azur",
"dropoff_type": "fbo",
"service_date": "2026-11-02",
"service_time": "08:50"
}'
A fuller one
Everything below is optional. The more you send, the less an operator has to ask you for.
{
"cid": 1054,
"serviceTypeId": 1,
"customer_ref": "PO-44821",
"pickup": "Fairmont Monte Carlo, 12 Avenue des Spelugues, 98000 Monaco",
"pickup_type": "address",
"dropoff": "LFMN/NCE - Nice Cote d Azur",
"dropoff_type": "fbo",
"service_date": "2026-11-02",
"service_time": "08:50",
"passenger_first": "Masaya",
"passenger_last": "Kaji",
"pax_count": 2,
"bag_count": 3,
"vehicle_id": "11",
"icao": "LFMN",
"city": "Nice",
"country": "France",
"tail_no": "N811TM",
"flight_no": "BAW327",
"etd": "11:35",
"meeting_point": "Lobby",
"stops": ["Hotel Metropole, Monte Carlo"],
"notes": "Two child seats. Driver to wait if the flight slips.",
"booker_name": "Alex Rowe",
"booker_email": "alex@example.com",
"booker_phone": "+1 713 555 0134"
}
ICAO/IATA - Airport name
for an airport. We do not make you look up our internal location ids, and an operator
confirms the exact FBO or terminal when the job is reviewed.Fields
| Field | Type | What it is |
|---|---|---|
| cid | integer | The GGT customer the job is for. Must be one your key is linked to — list them. |
| serviceTypeId | integer | What you are asking GGT to provide. One of four. |
| pickup | string | Where the car collects. Free text. |
| pickup_type | string | address, fbo or
commercial. Why it matters. |
| dropoff | string | Where it sets down. Free text. |
| dropoff_type | string | As above. |
| service_date | string | YYYY-MM-DD. Not in the past. |
| service_time | string | HH:MM, 24-hour, local to the pick-up. |
serviceTypeId — what you are asking for
| Value | Service |
|---|---|
| 1 | Executive Transport |
| 2 | Crew Transport |
| 3 | Security Trained Driver |
| 4 | Executive Protection Agent |
GGT runs other services — aircraft guard, security briefs, meet and greet, tour guides — but those are arranged with us directly rather than booked through the API.
pickup_type and dropoff_type
| Value | Means |
|---|---|
| address | A street address — a hotel, an office, a home. |
| fbo | A private aviation terminal. |
| commercial | A commercial airline terminal. |
trip_type so you can check we agreed
with you.| Field | Type | What it is |
|---|---|---|
| customer_ref | string | Your own reference. Comes back on invoices, and ties legs of one trip together. |
| passenger_first | string | Principal's first name. |
| passenger_last | string | Principal's surname. |
| pax_count | integer | People travelling. Defaults to 1. |
| bag_count | integer | Bags, so the vehicle fits them. |
| vehicle_id | string | An id or a name from the table below. Defaults to Executive Sedan. |
| icao | string | Airport, e.g. LFMN. |
| city | string | City, if it is not obvious from the address. |
| country | string | Country. |
| tail_no | string | Aircraft registration. |
| flight_no | string | Commercial flight number. |
| eta | string | Arrival time of the flight, HH:MM. |
| etd | string | Departure time of the flight, HH:MM. |
| meeting_point | string | Where to meet, e.g. Lobby. |
| stops | array of string | Intermediate stops, in order. |
| notes | string | Anything the operator and driver should know. Child seats, waiting time, a door number. |
| booker_name | string | Who asked for it. Defaults to the name on your account. |
| booker_email | string | Where GGT replies. Defaults to your account email. |
| booker_phone | string | A number to reach the booker on. |
Vehicles
Send vehicle_id as the id or the name — both are accepted.
| vehicle_id | Name | Typically |
|---|---|---|
| 7 | Executive Sedan | Mercedes E Class |
| 17 | Luxury Sedan | Mercedes S Class |
| 11 | Executive Van | Mercedes Sprinter |
| 42 | Standard SUV | Chevy Suburban |
| 75 | Luxury SUV | Cadillac Escalade |
| 33 | Luxury Minicoach | Shuttle Van |
| 117 | Crew Van | Ford Transit |
| 15 | Luxury Minivan | Mercedes V Class |
| 76 | Other | Other |
What gets checked
Every field is validated before anything is written, and you are told about all the problems at once rather than one refusal at a time. The same rules apply when you change a job.
| Field | Rule |
|---|---|
| cid | Must be a real GGT customer, must not be closed, and must be one your key is linked to. |
| service_date | YYYY-MM-DD, not in the past, not more than three years ahead. |
| service_time | HH:MM, 24-hour, two digits each side. 8:50 and 8:50 AM are refused. |
| icao | Checked against our airport list. IATA is accepted and converted — send LHR and you get EGLL back. |
| pickup | Checked against Google. See below. |
| dropoff | |
| serviceTypeId | Must be 1, 2, 3 or 4. See the list. |
| pickup_type | Must be address, fbo or commercial. |
| dropoff_type | |
| vehicle_id | Must be one we run — id or name. The error lists every valid one. |
| pax_count | 1–99. |
| bag_count | 0–99. |
| eta, etd | HH:MM, same as service_time. |
| booker_email | Must be a valid address. |
Addresses
Pick-ups and drop-offs go through Google's geocoder. Three outcomes:
- Found. Accepted, and the response carries a
resolvedobject showing what we understood it as — worth logging, so you can see when a caller's address was understood as somewhere surprising. - Found approximately. Accepted, with a
warningsentry naming what it matched. GGT's own airport style —LFMN/NCE - Nice Cote d Azur— matches approximately and is perfectly fine. - Not found at all. Refused with
address_not_found. A driver cannot be sent to a string that is not a place.
{
"ok": false,
"error": {
"code": "validation_failed",
"message": "3 fields need attention.",
"fields": [
{ "field": "service_date", "code": "bad_date",
"message": "Must look like 2026-11-04 (YYYY-MM-DD, four-digit year first).", "got": "04/11/2026" },
{ "field": "icao", "code": "unknown_icao",
"message": "Not an airport we have on file. Use the ICAO (EGLL) or the IATA (LHR).", "got": "XXXX" },
{ "field": "dropoff", "code": "address_not_found",
"message": "We could not find that location. Give a place name, street and town — or for an airport, the ICAO and name, like \"LFMN/NCE - Nice Cote d Azur\".",
"got": "asdkjh 99999" }
]
},
"request_id": "c41e7a09b2d85f36"
}
What comes back
201 Created, and the booking number.
{
"ok": true,
"ggt_id": 368952,
"cid": 1054,
"customer": "Your Company Ltd",
"customer_ref": "PO-44821",
"status": "Needs Quote/Confirm",
"status_id": 11,
"service_date": "2026-11-02",
"service_time": "08:50",
"serviceTypeId": 1,
"service": "Executive Transport",
"trip_type": "Departure",
"pickup": "Fairmont Monte Carlo, 12 Avenue des Spelugues, 98000 Monaco",
"dropoff": "LFMN/NCE - Nice Cote d Azur",
"message": "Created. A GGT operator reviews it before anything is sent to a supplier.",
"request_id": "a3f91c20d4e8b771"
}
Store the ggt_id. It is how GGT refers to the trip in
every later conversation, email and invoice.
Change a job
Correct a job you created. You can only touch jobs belonging to a
cid your key is linked to — anything else answers
404, exactly as if it did not exist.
Two required things: the ggt_id, and what to change.
curl -X PATCH https://api.ggtinternal.com/v1/jobs \
-H "X-API-Key: ggt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{ "ggt_id": 368950, "field": "service_time", "value": "09:15" }'
Several at once, if you prefer:
{
"ggt_id": 368950,
"fields": {
"service_time": "09:15",
"pax_count": 3,
"passenger_last": "Kajiyama"
}
}
/api/dev/v1/jobs_edit instead. Same code behind it.What comes back
{
"ok": true,
"ggt_id": 368950,
"cid": 1054,
"mode": "live",
"changed": ["service_time"],
"before": { "service_time": "08:50" },
"after": { "service_time": "09:15" },
"message": "Updated service_time.",
"request_id": "b8c4e1d79a2f0356"
}
You get before and after so your
own audit trail can record what actually moved, rather than what you intended to move.
What can be changed, and when
Only these fields. The customer, the status, the vendor, the booking number and anything to do with money are set by GGT and are not reachable from here.
| Always editable | Editable until the job is confirmed |
|---|---|
customer_ref, passenger_first,
passenger_last, pax_count,
bag_count, tail_no,
flight_no, eta,
etd, meeting_point |
pickup, dropoff,
service_date, service_time,
vehicle_id, icao,
city, country,
pickup_type, dropoff_type,
serviceTypeId |
409 job_locked — call us and we will move it. Correcting a
passenger's name or a flight number never moves a car, so that stays open throughout.
A service that was due more than an hour ago answers
409 service_started.Read a job back
Returns the job as it now stands, including the status an operator has moved it to. Works for jobs belonging to any customer your key is linked to — however the job was created, so a trip somebody booked by email shows up here too.
curl "https://api.ggtinternal.com/v1/jobs?ggt_id=368952" \ -H "X-API-Key: ggt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
A job belonging to a customer you are not linked to answers
404 not_found — the same as one that does not exist. Whether a
booking exists is not something this API will confirm to somebody with no business knowing.
Your customers
The customer accounts this key may create jobs for. Only yours — this is not a directory of GGT's clients.
{
"ok": true,
"count": 2,
"customers": [
{ "cid": 1054, "company": "Your Company Ltd", "department": null },
{ "cid": 1180, "company": "Another Account", "department": "Trip Support" }
],
"request_id": "7bd0115ac9e34428"
}
Check a key
Creates nothing. Run it first, and run it again whenever something stops working — it tells you whether the key is good, which environment it is, and whether the account is allowed to create jobs yet.
{
"ok": true,
"account": { "company": "Your Company Ltd", "email": "alex@example.com", "status": "approved" },
"key": { "label": "Production server", "environment": "live" },
"can_create_jobs": true,
"why": "This key creates real jobs.",
"customer_ids": [ { "cid": 1054, "company": "Your Company Ltd" } ],
"server_time_utc": "2026-10-19T13:06:52Z",
"request_id": "19c7a84be0d2f563"
}
Sandbox
A test key puts every call in the sandbox. Same URLs, same validation, same
errors, same shape of answer — but jobs are written to a test table and never reach the
reservation system, and the booking number starts at
9,000,001 so it is
obviously not a real one.
"test": true flag and no separate hostname. That is
deliberate: a flag can be left on by accident and a hostname can be forgotten, and both
mistakes are the kind you only discover from a car at an airport. Going to production is
one changed string in your config.Try it without writing any code
The portal has a live console: pick an endpoint, edit the JSON, press send, read the real response. Useful for seeing what a validation failure looks like before you have to handle one.
Your key is not on that page. The console posts to a server-side helper that holds the key — the same rule as everywhere else, applied to our own screen.
What is different
| Test key — sandbox | Live key | |
|---|---|---|
| URLs | identical | |
| Validation | identical, including the Google address check | |
POST /v1/jobs |
writes to the sandbox table | books for real in RES |
PATCH /v1/jobs |
edits sandbox jobs | edits real jobs, and RES with them |
GET /v1/jobs |
reads sandbox jobs only | reads real jobs |
| Booking numbers | from 9,000,001 | real GGT booking numbers |
mode in the reply |
"sandbox" | "live" |
| Anybody dispatched | never | after an operator reviews it |
Every reply carries mode. Assert on it in your test suite and
you can never ship a build that was quietly pointing at production.
{
"ok": true,
"mode": "sandbox",
"ggt_id": 9000001,
"cid": 1054,
"status": "Needs Quote/Confirm",
"message": "Created in the sandbox. Nothing real was booked and no car exists. Use a live key when you are ready.",
"request_id": "0d7e4f1a93b6c258"
}
Errors
A failure is JSON too, with a stable error.code worth
branching on and an error.message for a human. Where it helps,
the error carries the detail that makes it fixable — which field, which cid, what was allowed.
{
"ok": false,
"error": {
"code": "cid_not_allowed",
"message": "This key is not linked to customer 999. GET /api/dev/v1/customers lists the ones it may book for.",
"cid": 999,
"allowed_cids": [1054, 1180]
},
"request_id": "5f2ab8c17d904e36"
}
| HTTP | code | What to do |
|---|---|---|
| 400 | missing_field | One of the eight required fields is absent. error.fields names them. |
| 400 | validation_failed | One or more fields are wrong. error.fields is a list, each with its own code. See the rules. |
| 400 | bad_json | The body did not parse. Check Content-Type and the JSON. |
| 400 | bad_date | service_date is not YYYY-MM-DD. |
| 400 | bad_time | service_time is not HH:MM, 24-hour. |
| 400 | date_in_past | The service date has already been. Check the timezone you built it in. |
| 400 | date_too_far | More than three years ahead — almost always a typo in the year. |
| 400 | address_not_found | Google could not find that pick-up or drop-off at all. |
| 400 | address_too_short | Too short to send a driver to. |
| 400 | unknown_icao | Not an airport on our list. ICAO or IATA both accepted. |
| 400 | bad_service_type | serviceTypeId is not 1–4. |
| 400 | bad_place_type | A pickup_type or dropoff_type
that is not address, fbo or commercial. |
| 400 | bad_vehicle | Unknown vehicle_id. The error lists every valid one. |
| 400 | field_not_editable | You tried to change something the API does not expose. The editable list. |
| 400 | nothing_to_change | A PATCH with no editable field in it. |
| 401 | no_api_key | No X-API-Key header arrived. |
| 401 | bad_api_key | Unknown or revoked key. Check the portal. |
| 403 | not_approved | Your account is not linked to any customer yet. Nothing you can send fixes this — we do it. |
| 403 | cid_not_allowed | That cid is not one of yours. |
| 403 | unknown_cid | No GGT customer has that id at all. |
| 403 | inactive_cid | That customer account is closed. |
| 403 | account_suspended | Write to api@ggtworldwise.com. |
| 404 | not_found | No such job, or not one of yours. |
| 409 | job_locked | The job is confirmed; that field can no longer be moved by API. See which. |
| 409 | service_started | The service was due over an hour ago. Call us. |
| 405 | method_not_allowed | Wrong HTTP method. The Allow header says which are right. |
| 429 | rate_limited | Slow down. See below. |
| 424 | reservations_rejected | Our reservation system refused it. Nothing was created. Send us the request_id. |
| 424 | no_booking_number | Rare. The booking may exist without a number reaching you — do not retry, contact us. |
| 424 | reservations_unavailable | Our reservation system did not answer. Nothing was created. Retry shortly. |
Retrying safely
A timed-out create is the normal way a duplicate booking happens: your side gives up, you
send it again, and two cars turn up. Send an
Idempotency-Key header — any string you generate per booking
attempt — and a repeat returns the first job rather than making a second.
curl -X POST https://api.ggtinternal.com/v1/jobs \
-H "X-API-Key: ggt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: booking-44821-leg1" \
-d '{ "cid": 1054, "pickup": "…", "dropoff": "…",
"service_date": "2026-11-02", "service_time": "08:50" }'
The replay answers 200 rather than
201, carries "idempotent_replay": true
and an Idempotent-Replay: true header. Use one key per booking
you intend to make — not one per process, or your second genuine booking will be swallowed.
424 reservations_unavailable and
424 reservations_rejected both mean nothing was created. A
424 no_booking_number does not — contact us instead.
Those three are
424 rather than a 5xx
because this site sits behind a proxy that replaces origin 5xx responses with its own HTML
page — you would get that instead of the JSON telling you what happened.Rate limits
60 requests per minute per key, counted over the
trailing minute. Over it you get 429 rate_limited and a
Retry-After header.
If you need more for a bulk load, write to us rather than spreading the work over several keys — we would rather raise the limit than guess why one account has eleven keys.
Going live
When the sandbox is doing what you want, this is the whole list:
- Make a live key on the keys page. It only appears once your account is linked to customer accounts.
- Swap the key in your config. Nothing else changes — not the URL, not the body, not your error handling.
- Call
GET /v1/pingand check"mode": "live". - Create one real job, and look at it in GGT with us on the phone the first time.
What we do not store
- No payment details, ever. This API has no card, bank or payment field, and
anything of the sort in
notesis a mistake — send it to us another way. Billing runs on the account, not on the booking call. - Request bodies are kept against your account and shown on your usage page, so you can see what you sent when a call fails. Treat them as you would your own logs: no card numbers, no passport numbers, nothing you would not want kept.
- Passenger names are kept because the driver needs them. Send an alias instead where a principal would rather not be named — GGT uses the alias throughout if one is set.