GGT WORLDWISE developers
OverviewGetting accessAuthenticationConventionsCreate a jobFieldsWhat gets checkedWhat comes backChange a jobRead a job backYour customersCheck a keySandboxErrorsRetrying safelyRate limitsTest and liveWhat we do not store

GGT WORLDWISE Booking API

Create job requests from your own system · v1 · revised October 19, 2026

You are not signed in. Everything here is readable without an account. To get a key, ask for access — the examples below will then show your own key instead of a placeholder.

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

  1. Register at api.ggtinternal.com and confirm your email.
  2. A test key is issued straight away. It authenticates and reads, but will not create a job.
  3. GGT links your account to the customer accounts you book for. This is a person's decision, not an automatic one.
  4. Once linked, create a live key in the portal. That key creates real jobs.
Why the wait. A 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.

Every request
X-API-Key: ggt_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Keeping a key safe.
  • 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

Base URL
https://api.ggtinternal.com/v1
  • HTTPS only. Plain HTTP is redirected; do not follow the redirect with a key attached.
  • JSON in, JSON out. Send Content-Type: application/json on a POST.
  • Dates are YYYY-MM-DD. Times are HH: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 ok and request_id. Quote the request_id when 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

POSThttps://api.ggtinternal.com/v1/jobs

The smallest call that works — the eight required fields:

curl
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.

JSON body
{
  "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"
}
Addresses are free text on purpose. Write them the way you would tell a driver: a hotel name and street, or 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

Required
FieldTypeWhat it is
cidinteger The GGT customer the job is for. Must be one your key is linked to — list them.
serviceTypeIdinteger What you are asking GGT to provide. One of four.
pickupstring Where the car collects. Free text.
pickup_typestring address, fbo or commercial. Why it matters.
dropoffstring Where it sets down. Free text.
dropoff_typestring As above.
service_datestring YYYY-MM-DD. Not in the past.
service_timestring HH:MM, 24-hour, local to the pick-up.

serviceTypeId — what you are asking for

ValueService
1Executive Transport
2Crew Transport
3Security Trained Driver
4Executive 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

ValueMeans
addressA street address — a hotel, an office, a home.
fboA private aviation terminal.
commercialA commercial airline terminal.
These do more work than they look like they do. Between them they tell us whether the job is an arrival, a departure or neither, which drives how the trip is briefed, when the driver is told to be there, and how flight delays are handled. A leg starting at an airport is an arrival; otherwise one ending at an airport is a departure; anything else is non-flight related. You do not send that — we work it out, and it comes back as trip_type so you can check we agreed with you.
Optional
FieldTypeWhat it is
customer_refstring Your own reference. Comes back on invoices, and ties legs of one trip together.
passenger_firststringPrincipal's first name.
passenger_laststringPrincipal's surname.
pax_countintegerPeople travelling. Defaults to 1.
bag_countintegerBags, so the vehicle fits them.
vehicle_idstring An id or a name from the table below. Defaults to Executive Sedan.
icaostringAirport, e.g. LFMN.
citystringCity, if it is not obvious from the address.
countrystringCountry.
tail_nostringAircraft registration.
flight_nostringCommercial flight number.
etastringArrival time of the flight, HH:MM.
etdstringDeparture time of the flight, HH:MM.
meeting_pointstringWhere to meet, e.g. Lobby.
stopsarray of stringIntermediate stops, in order.
notesstring Anything the operator and driver should know. Child seats, waiting time, a door number.
booker_namestringWho asked for it. Defaults to the name on your account.
booker_emailstringWhere GGT replies. Defaults to your account email.
booker_phonestringA number to reach the booker on.

Vehicles

Send vehicle_id as the id or the name — both are accepted.

vehicle_idNameTypically
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.

FieldRule
cidMust be a real GGT customer, must not be closed, and must be one your key is linked to.
service_dateYYYY-MM-DD, not in the past, not more than three years ahead.
service_timeHH:MM, 24-hour, two digits each side. 8:50 and 8:50 AM are refused.
icaoChecked against our airport list. IATA is accepted and converted — send LHR and you get EGLL back.
pickupChecked against Google. See below.
dropoff
serviceTypeIdMust be 1, 2, 3 or 4. See the list.
pickup_typeMust be address, fbo or commercial.
dropoff_type
vehicle_idMust be one we run — id or name. The error lists every valid one.
pax_count1–99.
bag_count0–99.
eta, etdHH:MM, same as service_time.
booker_emailMust be a valid address.

Addresses

Pick-ups and drop-offs go through Google's geocoder. Three outcomes:

  • Found. Accepted, and the response carries a resolved object 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 warnings entry 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.
If Google is having a bad day — unreachable, or over quota — the address is let through with a warning saying it was accepted unchecked. Turning away real bookings because a third party is down would be worse than accepting an address a GGT operator is going to read anyway.
400 — several problems at once
{
  "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.

201 Created
{
  "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

PATCHhttps://api.ggtinternal.com/v1/jobs

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 — one field
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:

JSON body — several fields
{
  "ggt_id": 368950,
  "fields": {
    "service_time": "09:15",
    "pax_count": 3,
    "passenger_last": "Kajiyama"
  }
}
If your client cannot send a PATCH — and plenty of corporate proxies cannot — post the identical body to /api/dev/v1/jobs_edit instead. Same code behind it.

What comes back

200 OK
{
  "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 editableEditable 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
Why the second column locks. Once an operator has confirmed the job, a supplier is holding a car for a particular place at a particular time. Moving that by API would send a driver somewhere nobody told him about. Those fields answer 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

GEThttps://api.ggtinternal.com/v1/jobs?ggt_id={ggt_id}

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
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

GEThttps://api.ggtinternal.com/v1/customers

The customer accounts this key may create jobs for. Only yours — this is not a directory of GGT's clients.

200 OK
{
  "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

GEThttps://api.ggtinternal.com/v1/ping

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.

200 OK
{
  "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.

The key decides, and nothing else does. There is no "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 — sandboxLive key
URLsidentical
Validationidentical, including the Google address check
POST /v1/jobs writes to the sandbox tablebooks for real in RES
PATCH /v1/jobs edits sandbox jobsedits real jobs, and RES with them
GET /v1/jobs reads sandbox jobs onlyreads real jobs
Booking numbersfrom 9,000,001 real GGT booking numbers
mode in the reply "sandbox""live"
Anybody dispatchedneverafter 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.

201 Created — sandbox
{
  "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.

403 Forbidden
{
  "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"
}
HTTPcodeWhat to do
400missing_field One of the eight required fields is absent. error.fields names them.
400validation_failed One or more fields are wrong. error.fields is a list, each with its own code. See the rules.
400bad_json The body did not parse. Check Content-Type and the JSON.
400bad_date service_date is not YYYY-MM-DD.
400bad_time service_time is not HH:MM, 24-hour.
400date_in_past The service date has already been. Check the timezone you built it in.
400date_too_far More than three years ahead — almost always a typo in the year.
400address_not_found Google could not find that pick-up or drop-off at all.
400address_too_short Too short to send a driver to.
400unknown_icao Not an airport on our list. ICAO or IATA both accepted.
400bad_service_type serviceTypeId is not 1–4.
400bad_place_type A pickup_type or dropoff_type that is not address, fbo or commercial.
400bad_vehicle Unknown vehicle_id. The error lists every valid one.
400field_not_editable You tried to change something the API does not expose. The editable list.
400nothing_to_change A PATCH with no editable field in it.
401no_api_key No X-API-Key header arrived.
401bad_api_key Unknown or revoked key. Check the portal.
403not_approved Your account is not linked to any customer yet. Nothing you can send fixes this — we do it.
403cid_not_allowed That cid is not one of yours.
403unknown_cid No GGT customer has that id at all.
403inactive_cid That customer account is closed.
403account_suspended Write to api@ggtworldwise.com.
404not_found No such job, or not one of yours.
409job_locked The job is confirmed; that field can no longer be moved by API. See which.
409service_started The service was due over an hour ago. Call us.
405method_not_allowed Wrong HTTP method. The Allow header says which are right.
429rate_limited Slow down. See below.
424reservations_rejected Our reservation system refused it. Nothing was created. Send us the request_id.
424no_booking_number Rare. The booking may exist without a number reaching you — do not retry, contact us.
424reservations_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
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.

When it is safe to retry without one. 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:

  1. Make a live key on the keys page. It only appears once your account is linked to customer accounts.
  2. Swap the key in your config. Nothing else changes — not the URL, not the body, not your error handling.
  3. Call GET /v1/ping and check "mode": "live".
  4. Create one real job, and look at it in GGT with us on the phone the first time.
Keep the test key. Point your CI and your staging environment at it permanently. A test suite that runs against a live key eventually books something, usually at 3am, usually on the largest account.

What we do not store

  • No payment details, ever. This API has no card, bank or payment field, and anything of the sort in notes is 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.
GGT WORLDWISE · Documentation · api@ggtworldwise.com