Start

Authentication

OAuth 2.0 against Auth0, in two flows. Discovery needs no token; quoting and checkout do.

Two things that cost people an afternoon

The audience is an identifier, not an address. It looks like a URL and is never fetched. Requesting a token without it, or trying to call it, are the two most common first mistakes.

Dynamic client registration is disabled on the tenant. An MCP client that registers itself will complete discovery and then fail at the authorize step with nothing useful in the error. This is not a dead end — it means your client, including any redirect URIs, is registered by hand instead. See request access.

Which flow you need

Both flows issue tokens for the same audience against the same scopes. The grant differs; what a token is allowed to do does not.

You areUse
A server calling us as yourself, with no traveler presentClient credentials
A connector or app acting for a person who signs inAuthorization code with PKCE (S256)

Client credentials

curl --request POST \
  --url https://swisstouristy.eu.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "audience": "https://api.swisstouristy.com/public/v1",
    "grant_type": "client_credentials"
  }'

Then send it as an ordinary bearer token. The scheme match is case-insensitive.

curl https://swisstouristy.com/api/public/v1/quotes \
  --header "Authorization: Bearer $ST_ACCESS_TOKEN"

Authorization code with PKCE

For a connector acting on behalf of a person. PKCE is required and S256 is the only accepted challenge method: a public client ships no usable secret, so the proof key is the only thing protecting the code. Your redirect URI must be registered with us before the first attempt — an unregistered one fails at the authorize step.

# 1. Send the person here.
https://swisstouristy.eu.auth0.com/authorize
  ?response_type=code
  &client_id=YOUR_CLIENT_ID
  &redirect_uri=YOUR_REGISTERED_REDIRECT_URI
  &audience=https://api.swisstouristy.com/public/v1
  &scope=discovery%20quote%3Acreate%20quote%3Aread-own
  &state=YOUR_OPAQUE_STATE
  &code_challenge=BASE64URL_SHA256_OF_VERIFIER
  &code_challenge_method=S256

# 2. Exchange the code they come back with.
curl --request POST \
  --url https://swisstouristy.eu.auth0.com/oauth/token \
  --header 'content-type: application/json' \
  --data '{
    "grant_type": "authorization_code",
    "client_id": "YOUR_CLIENT_ID",
    "code": "CODE_FROM_REDIRECT",
    "redirect_uri": "YOUR_REGISTERED_REDIRECT_URI",
    "code_verifier": "YOUR_ORIGINAL_VERIFIER"
  }'

The resulting token is used exactly as above. Ask only for the scopes you need: a token carrying checkout:prepare can hand a traveler to checkout, and one that does not, cannot.

Scopes

Least privilege, with no superset. A broader scope is never accepted as a substitute for the right one, and there is no admin or wildcard scope on this surface.

ScopeWhat it permits
checkout:prepareHand a traveler to Swiss Touristy's hosted checkout for a live quote of your own. Creates no booking and takes no payment.
discoveryConnect to the MCP server. Required by the MCP transport on every connection; no REST operation requires it, and the REST discovery endpoints are anonymous.
quote:createCreate a quote of your own.
quote:read-ownRead a quote you created. Never another caller's.

discovery is required by the MCP transport on every connection, and by no REST operation — the REST discovery endpoints are anonymous. If you are integrating over MCP, request it; if you are only calling REST, you will never need it.

Which operations need what

No token (8)

  • GET /services/transfers
  • GET /routes
  • GET /routes/{route_key}
  • GET /vehicle-classes
  • GET /policies/{policy_key}
  • GET /quote-requirements
  • POST /quote-intents
  • GET /checkout-sessions/{quote_id}

Token required (3)

  • POST /quotesquote:create
  • GET /quotes/{quote_id}quote:read-own
  • POST /checkout-sessionscheckout:prepare

When it fails

Missing, expired, malformed and insufficient-scope tokens all return the same AUTHENTICATION_REQUIRED. That is deliberate — telling a caller which of those it was is a probing aid, and a legitimate integration knows which token it sent. Repeated failures are throttled against their own tier, so a credential-guessing loop slows itself down.