# Troubleshooting the APIs

> The errors the booking API, GraphQL and the MCP server answer with, what each one means and what to do about it.

Source: https://docs.bifrostorbit.com/travel/developers/troubleshooting/

Every error from the booking API has the same shape. The `message` is written so you can show it to a traveller as it is:

```json
{ "error": { "code": "conflict", "message": "Only 2 places are left on this departure" } }
```

GraphQL puts the same `message` in `errors`, with the code under `extensions.code`. See [GraphQL](https://docs.bifrostorbit.com/travel/developers/graphql/) for its own codes, such as `query_too_deep`.

## Calls from the browser

| You see | Why | What to do |
| --- | --- | --- |
| `403` 'This website isn't allowed to book here. Add it to the allowed websites in Travel Orbit's settings.' | A quote, booking or portal call came from a website that isn't under **Allowed websites**. | An Admin adds the website under **Booking settings**, such as `https://northwilds.com`. Only the scheme, host and port count, so `https://www.northwilds.com` is a different website. Up to 20 can be listed. |
| A CORS error in the browser console, with no answer | The same as above: the answer has no `Access-Control-Allow-Origin` for your website. | As above. Reading trips works from any website. |
| A CORS error on a request with your own headers | Only `Content-Type`, `Authorization` and `X-Api-Key` are allowed. | Leave out other headers. |
| `403` 'A key that reads drafts must stay on your server…' | A `trv_sk_` key was sent from a browser on another website. | Call from your server, or use a `trv_pk_` key in the browser. |
| The portal answers `401` 'Sign in with the code we email you' right after signing in | The browser doesn't send the `travel.portal` cookie to Travel Orbit from your website. | Call the portal endpoints from your own server and pass the cookie on. See [the booking API](https://docs.bifrostorbit.com/travel/developers/booking-api/). |

## Keys and organisations

| You see | Why | What to do |
| --- | --- | --- |
| `402` `subscription_inactive` | The organisation's Travel Orbit trial or plan has ended, so the booking pages and API are paused. | An owner chooses a plan under **Billing** in Orbit; everything answers again within a minute. Show your own "temporarily unavailable" message meanwhile. |
| `404` 'Organisation was not found' | The booking name in the address, or in `?organisation=`, doesn't exist. | Copy it from **Booking settings**, such as `northwilds`. |
| `401` 'This API key isn't valid for this organisation…' | The key is wrong, or belongs to another organisation than the booking name. | Check the key under **API keys**, and that it matches the booking name. |
| `401` 'This API key has been revoked…' or '…has expired…' | The key was revoked, or passed its expiry date. | Create a new key. A wrong key fails even on reads that need none. |
| `401` 'Send an API key, or name the organisation with ?organisation=…' | A GraphQL request with neither. | Add `?organisation=northwilds` or a key. |
| A draft trip is missing | Drafts need a `trv_sk_` key. | Send a key with **Read drafts**, from your server. Quotes and bookings never accept draft trips: they answer `404` 'Trip was not found'. |

## Quotes and bookings

| You see | Why |
| --- | --- |
| `422` 'Choose a departure', 'Add your first name', 'Add an email address we can reach you at', 'Accept the booking terms to continue' | A field is missing or in the wrong format. `issues` lists each one. |
| `400` 'Add the passport number for traveller 2' | The trip's **Booking form** asks for that detail. Read the trip's `travellerFields`. |
| `400` 'Answer "Boot size" for traveller 1' | A required question of the trip has no answer. Read the trip's `questions`. |
| `400` 'This trip isn't taking bookings at the moment' | The trip's `bookingMode` is `closed`. |
| `400` 'This departure isn't taking bookings', 'This departure is cancelled', 'This departure has already left', 'Bookings for this departure have closed' | The departure is closed, cancelled, in the past or past its booking deadline. |
| `400` 'This trip isn't sold in SEK' | `currency` isn't one of the trip's `currencies`. |
| `400` 'That code isn't valid', 'That code has expired', 'That code has been used up', 'That code is not valid for this trip' | The discount code doesn't exist or doesn't apply. Other messages say it isn't active, isn't valid yet, or isn't valid for the date or currency. |
| `400` 'Gear hire can't be added this close to departure' | The extra has a deadline before departure. |
| `400` 'Online payment isn't working right now. Try again in a moment, or contact us to book.' | Stripe refused to start the payment. The places are released. Check the organisation's Stripe key under **Stripe**. |
| `409` 'This departure is full', 'Only 2 places are left on this departure' | Fewer places than travellers. |
| `409` 'Double room is sold out on this departure' | A package or extra has no places left. |
| `409` 'Those places were just taken. Choose another departure or fewer travellers.' | Someone else booked the last places at the same moment. |

### A booking comes back as `requested`

Nothing is charged and `payment` is `null` when the trip takes requests (`bookingMode: "request"`), or when the organisation has no working Stripe key (`payments.online` is `false` on the organisation). See [Connect your Stripe account](https://docs.bifrostorbit.com/travel/everyone/connect-stripe/).

### A booking stays `held`

A held booking waits for its first payment, for 15 minutes unless an Admin changed **Hold places for** in Booking settings (5 to 120 minutes). After `stripe.confirmPayment`, call `POST /bookings/{reference}/check` with the lead traveller's email: it asks Stripe and confirms the booking if the payment went through. If nobody pays in time, the booking becomes `expired` and the places go back on sale. Don't make a second booking: let the traveller try again with the same `clientSecret`.

## The traveller account

| You see | Why |
| --- | --- |
| `{ "sent": true }` but no email arrives | Codes are only sent to an email that made a confirmed, requested or cancelled booking, and at most once every 45 seconds. |
| `400` 'That code isn't right. Check the email and try again.' | A wrong code. After 5 wrong tries the code stops working. |
| `400` 'That code has expired. Ask for a new one.' | Codes last 10 minutes. |
| `400` 'Nothing is due on this booking' | Every payment is made. |
| `400` 'This booking can't take payments' | Only confirmed bookings take payments. |
| `400` 'Pay once by card first, and we will use that card for the next payments' | Automatic payments need a saved card. |
| `400` 'This trip has already started, so the details can't be changed here' | Travellers can change their details only before departure. |

## Rate limits

`429` with 'Too many tries. Wait a minute and try again.' (GraphQL: 'Too many requests…') means one address made too many requests in a short time. The limits per address and organisation are in [the booking API](https://docs.bifrostorbit.com/travel/developers/booking-api/). If your server calls on behalf of many visitors, all its calls share its address, so cache reads on your side.

## The MCP server

| You see | Why |
| --- | --- |
| `401` 'Connect the assistant to Travel Orbit again' | The token is missing, wrong or expired and couldn't be renewed. Connect again from the assistant. |
| `403` 'The person who connected this assistant no longer has Travel Orbit in the organisation' | Their access was removed in Orbit. |
| 'Your Travel Orbit role doesn't allow that. Ask an admin of your organisation.' | A Viewer used a tool that writes. Editors and Admins can. |
| 'Assistants can't change prices, payment plans, discount codes or cancellation terms…' | `update_trip_text` would have changed a price or terms. |
| `invalid_redirect_uri` 'Give up to ten https addresses, or http on localhost' | The client registered an address that isn't allowed. |
| `invalid_client_metadata` 'Only public clients using PKCE are supported' | The client asked for a client secret. |
| `invalid_grant` 'The code has expired or was used', 'The code verifier does not match', 'Sign in again' | The sign-in took longer than a minute, the PKCE verifier is wrong, or the refresh token expired. Start the sign-in again. |
| 'This app is not registered with Travel Orbit. Connect it again from the app.' on the sign-in page | The client registration was forgotten. The app should register again. |

## Something failed on our side

`500` with `server_error` means Travel Orbit couldn't answer. Try again in a moment. The status of every Orbit service is at [docs.bifrostorbit.com/status](https://docs.bifrostorbit.com/status/).
