> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://api-docs.globalairportconcierge.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://api-docs.globalairportconcierge.com/_mcp/server.

# Introduction

The **Global Airport Concierge API v1** lets a partner price and book airport concierge services: Meet & Assist, Porter, Lounge Access and more, at over 500 airports.

**Base URL:** `https://api-prod.globalairportconcierge.com/api/v1`

GAC issues your credentials, and a sandbox base URL if you want one, when your integration is set up.

## Getting started

1. **Get access token** — exchange your `client_id` and `client_secret` for a bearer token. Send it as `Authorization: Bearer …` on everything else. Tokens last a year, so cache it.
2. **List services at an airport** — find out what is available and at what rate.
3. **Create quote** — send the itinerary and get a price back. The booking is created in status `quote`.
4. **Confirm quote** — commit it. Status becomes `booking` and the customer is emailed.
5. **Look it up later** — by our id, our reference, or your own.

Endpoints are numbered in the order you would normally call them.

## Booking lifecycle

```
         Create quote
              |
           [quote]  --- Replace / Reprice ---> [quote]
              |
        Confirm quote
              |
          [booking]
              |
        Cancel booking
              |
         [cancelled]
```

`cancelled` is terminal. Every other endpoint refuses a cancelled booking.

A quote is valid for **one week**. After that, reprice it before confirming.

## Response envelope

Every endpoint except **Get access token** wraps its payload:

```json
{ "message": "…", "status": 200, "responseTime": "125ms", "trace": [], "data": { } }
```

List endpoints add a `pagination` block and put an array in `data`:

```json
"pagination": { "total_count": 7, "total_pages": 4, "current_page": 1, "limit": 2 }
```

## Errors

Every error carries an `error_code`. **Branch on `error_code`, not on the message.** The code is a contract; the wording is ours to improve.

```json
{
  "message": "No booking found with id 99999999. It may not exist, or it may belong to another company.",
  "status": 404,
  "responseTime": "22ms",
  "error_code": "booking_not_found",
  "data": { "errors": [] }
}
```

Validation failures add a top-level `errors` object keyed by field, and report **every** problem at once rather than stopping at the first:

```json
{
  "message": "The currency field is required. (and 2 more errors)",
  "status": 422,
  "error_code": "validation_failed",
  "errors": {
    "currency": ["The currency field is required."],
    "journeys": ["At least one journey is required."]
  },
  "data": []
}
```

| `error_code`                 | Status | Meaning                                                                                                             |
| ---------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------- |
| `validation_failed`          | 422    | Your payload is wrong. `errors` says exactly where. Fix and resend.                                                 |
| `invalid_client_credentials` | 401    | `client_id` or `client_secret` was rejected when requesting a token.                                                |
| `unauthenticated`            | 401    | Bearer token missing, invalid or expired. Get a new one.                                                            |
| `forbidden`                  | 403    | Your client is not allowed to do this.                                                                              |
| `invoice_terms_required`     | 403    | Your company is not set up for invoice payments, so bookings cannot be confirmed.                                   |
| `booking_not_found`          | 404    | No booking with that id or reference, or it belongs to another company. The two are deliberately indistinguishable. |
| `endpoint_not_found`         | 404    | No such endpoint. Check the method and path.                                                                        |
| `method_not_allowed`         | 405    | Wrong HTTP method for that path.                                                                                    |
| `booking_cancelled`          | 409    | The booking is cancelled and cannot be changed. Terminal, do not retry.                                             |
| `booking_already_confirmed`  | 409    | Already confirmed. Read it back rather than confirming again.                                                       |
| `quote_expired`              | 409    | Reprice the quote, then confirm.                                                                                    |
| `payment_incomplete`         | 409    | Payment has not settled yet. Retry later.                                                                           |
| `booking_not_confirmable`    | 409    | The booking's state blocks confirmation. Read it back to see why.                                                   |
| `booking_total_too_low`      | 422    | No chargeable total. Check a service is attached and priced.                                                        |
| `rate_limit_exceeded`        | 429    | Too many requests. Back off and retry.                                                                              |
| `server_error`               | 500    | Our fault. Nothing was changed. The message carries a reference to quote to GAC support.                            |

A `4xx` other than `429` will not succeed on retry without a change on your side. `429` and `500` are the only ones worth retrying as-is.

## Pricing inputs

Services are priced from the journey's `adult`, `child` and `infant` counts and its bag counts. There is no per-service passenger override, so those counts must be accurate.

| Service        | Priced on                                                                    |
| -------------- | ---------------------------------------------------------------------------- |
| Meet & Assist  | `adult + child`, from a nine-tier cumulative table. Infants are not counted. |
| Porter Service | `bag_small + bag_medium + bag_large`.                                        |
| Fast Track     | `adult + child`.                                                             |
| Lounge Access  | `adult`, `child` and `infant` charged separately, each at its own rate.      |
| Electric Buggy | `adult + child`, in bands.                                                   |

Lounge Access is the only service that charges infants, and only where the airport has a non-zero infant rate.

## Rate limiting

300 requests per minute. Over that you get `429 rate_limit_exceeded`. No `Retry-After` header is sent, so implement your own backoff rather than retrying immediately. If you expect sustained volume above this, talk to GAC before you go live.

## Known quirks

These are real behaviours, documented so you do not lose a day to them.

| Behaviour                                                                                                                                  | What to do                                                                                               |
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `confirm: true` when creating a quote does not confirm anything. The booking stays a `quote` and no error is returned.                     | Call the confirm endpoint explicitly.                                                                    |
| Replacing a quote is a full replace. It regenerates `ref_no`, resets `status` to `quote`, clears `billing.status` and resets `expires_on`. | Echo the existing `ref_no` back, and never replace a confirmed booking unless you mean to un-confirm it. |
| `trace` is always `[]` on success, and the bookings list omits the key entirely.                                                           | Ignore it.                                                                                               |
| Quote validity is one week, not the four hours some older documentation implies.                                                           | Trust `expires_on`.                                                                                      |
| `X-Tenant-ID` is fixed server-side.                                                                                                        | Do not send it.                                                                                          |

## Not available yet

* **No webhooks.** Nothing notifies you when a booking changes. Poll the booking endpoint for anything you need to stay current on.
* **No idempotency key.** A retried create makes a second booking. Send a unique `settings.customer_reference` every time and use it to detect and recover from duplicates.
* **No date filter** when listing bookings. Reconciliation means paging newest-first.
* **Service discovery is partial.** The catalogue endpoint returns only three of the bookable services. Ask GAC for the ids of the rest.

Last reviewed against the live API: 2026-08-24. Every example in this documentation is a real captured response, including the error ones.