Start
Four calls: what a quote needs, a free estimate, an authoritative quote, and reading it back.
These are not illustrations. Each one is a real file in docs/public-api/examples/ that CI executes against a live server, and the text below is that file verbatim. If a sample here is wrong, the build is red.
One of the four calls needs no credential at all, so you can check the connection before you have one:
curl https://swisstouristy.com/api/public/v1/quote-requirementsFor the other three you need a token — see authentication. Then set two environment variables; no sample ever contains a literal credential.
export ST_API_BASE=https://swisstouristy.com/api/public/v1
export ST_ACCESS_TOKEN=...quickstart.sh#!/usr/bin/env bash
# Swiss Touristy public API — discovery to quote, in four calls.
#
# Run it:
# export ST_API_BASE=https://swisstouristy.com/api/public/v1
# export ST_ACCESS_TOKEN=... # see /developers/authentication
# ./quickstart.sh
#
# Never hardcode a credential here. This file is executed by CI and scanned by
# gitleaks on every change.
set -euo pipefail
: "${ST_API_BASE:?set ST_API_BASE}"
: "${ST_ACCESS_TOKEN:?set ST_ACCESS_TOKEN}"
echo "1. What does a quote need?"
curl -fsS "$ST_API_BASE/quote-requirements" | head -c 300
echo
echo "2. A non-binding estimate. No token, persists nothing."
curl -fsS -X POST "$ST_API_BASE/quote-intents" \
-H 'content-type: application/json' \
-d '{
"origin": "Zurich Airport",
"destination": "Lucerne",
"pickup_datetime": "2030-06-01T09:30:00+02:00",
"passengers": 2,
"luggage": 2
}' | head -c 300
echo
echo "3. An authoritative quote. Token + an idempotency key."
QUOTE=$(curl -fsS -X POST "$ST_API_BASE/quotes" \
-H "authorization: Bearer $ST_ACCESS_TOKEN" \
-H 'content-type: application/json' \
-H "idempotency-key: quickstart-$(date +%s)" \
-d '{
"origin": "Zurich Airport",
"destination": "Lucerne",
"pickup_datetime": "2030-06-01T09:30:00+02:00",
"passengers": 2,
"luggage": 2
}')
QUOTE_ID=$(printf '%s' "$QUOTE" | python -c 'import json,sys; print(json.load(sys.stdin)["quote_id"])')
TOTAL=$(printf '%s' "$QUOTE" | python -c 'import json,sys; d=json.load(sys.stdin); print(d["options"][0]["total_price"], d["options"][0]["currency"])')
echo " quote_id=$QUOTE_ID total=$TOTAL"
echo "4. Read it back. Only the caller who created it can."
curl -fsS "$ST_API_BASE/quotes/$QUOTE_ID" \
-H "authorization: Bearer $ST_ACCESS_TOKEN" | head -c 200
echo
echo "Done. The quote is a 30-minute price snapshot, not a reservation."
quickstart.py#!/usr/bin/env python3
"""Swiss Touristy public API — discovery to quote, in four calls.
Run it::
export ST_API_BASE=https://swisstouristy.com/api/public/v1
export ST_ACCESS_TOKEN=... # see /developers/authentication
python quickstart.py
Standard library only, so there is nothing to install before the first call
works. Never hardcode a credential here: this file is executed by CI and
scanned by gitleaks on every change.
"""
from __future__ import annotations
import json
import os
import sys
import time
import urllib.error
import urllib.request
BASE = os.environ.get("ST_API_BASE") or sys.exit("set ST_API_BASE")
TOKEN = os.environ.get("ST_ACCESS_TOKEN") or sys.exit("set ST_ACCESS_TOKEN")
JOURNEY = {
"origin": "Zurich Airport",
"destination": "Lucerne",
"pickup_datetime": "2030-06-01T09:30:00+02:00",
"passengers": 2,
"luggage": 2,
}
def call(method: str, path: str, body: dict | None = None, **headers: str) -> dict:
request = urllib.request.Request(
f"{BASE}{path}",
data=json.dumps(body).encode() if body is not None else None,
method=method,
headers={"content-type": "application/json", **headers},
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
return json.loads(response.read())
except urllib.error.HTTPError as error:
# Every failure is RFC 7807 problem+json. `code` is the stable thing to
# branch on; `next_action` tells an agent how to recover without a human.
problem = json.loads(error.read())
print(f" {problem['code']}: {problem['detail']}", file=sys.stderr)
print(f" next: {problem.get('next_action')}", file=sys.stderr)
print(f" docs: {problem['type']}", file=sys.stderr)
raise SystemExit(1)
def main() -> None:
print("1. What does a quote need?")
requirements = call("GET", "/quote-requirements")
print(f" {len(requirements.get('fields', []))} fields described")
print("2. A non-binding estimate. No token, persists nothing.")
estimate = call("POST", "/quote-intents", JOURNEY)
print(f" route_basis={estimate.get('route_basis', {}).get('authority')}")
print("3. An authoritative quote. Token + an idempotency key.")
quote = call(
"POST",
"/quotes",
JOURNEY,
authorization=f"Bearer {TOKEN}",
# Same key + same body replays the original response instead of
# producing a second quote. Retry a timeout safely.
**{"idempotency-key": f"quickstart-{int(time.time())}"},
)
option = quote["options"][0]
print(f" quote_id={quote['quote_id']} {option['total_price']} {option['currency']}")
print(f" {option['vehicle_class']}")
print("4. Read it back. Only the caller who created it can.")
again = call("GET", f"/quotes/{quote['quote_id']}", authorization=f"Bearer {TOKEN}")
assert again["quote_id"] == quote["quote_id"]
print(f" expires_at={again.get('expires_at')}")
print("Done. The quote is a 30-minute price snapshot, not a reservation.")
if __name__ == "__main__":
main()
quickstart.ts#!/usr/bin/env node
/**
* Swiss Touristy public API — discovery to quote, in four calls.
*
* Run it:
*
* export ST_API_BASE=https://swisstouristy.com/api/public/v1
* export ST_ACCESS_TOKEN=... # see /developers/authentication
* npx tsx quickstart.ts
*
* No dependencies — `fetch` is built in on Node 18+. Never hardcode a
* credential here: this file is executed by CI and scanned by gitleaks on every
* change.
*/
const BASE = process.env.ST_API_BASE;
const TOKEN = process.env.ST_ACCESS_TOKEN;
if (!BASE) throw new Error("set ST_API_BASE");
if (!TOKEN) throw new Error("set ST_ACCESS_TOKEN");
const JOURNEY = {
origin: "Zurich Airport",
destination: "Lucerne",
pickup_datetime: "2030-06-01T09:30:00+02:00",
passengers: 2,
luggage: 2,
};
interface Problem {
code: string;
detail: string;
next_action?: string;
type: string;
retryable: boolean;
}
async function call(
method: string,
path: string,
body?: unknown,
headers: Record<string, string> = {},
): Promise<any> {
const response = await fetch(`${BASE}${path}`, {
method,
headers: { "content-type": "application/json", ...headers },
body: body === undefined ? undefined : JSON.stringify(body),
});
if (!response.ok) {
// Every failure is RFC 7807 problem+json. Branch on `code`, never on
// `detail` (prose, may be reworded) or `status` alone (codes share one).
const problem = (await response.json()) as Problem;
console.error(` ${problem.code}: ${problem.detail}`);
console.error(` next: ${problem.next_action ?? "—"}`);
console.error(` docs: ${problem.type}`);
process.exit(1);
}
return response.json();
}
async function main(): Promise<void> {
console.log("1. What does a quote need?");
const requirements = await call("GET", "/quote-requirements");
console.log(` ${(requirements.fields ?? []).length} fields described`);
console.log("2. A non-binding estimate. No token, persists nothing.");
const estimate = await call("POST", "/quote-intents", JOURNEY);
console.log(` route_basis=${estimate.route_basis?.authority}`);
console.log("3. An authoritative quote. Token + an idempotency key.");
const quote = await call("POST", "/quotes", JOURNEY, {
authorization: `Bearer ${TOKEN}`,
// Same key + same body replays the original response instead of
// producing a second quote. Retry a timeout safely.
"idempotency-key": `quickstart-${Date.now()}`,
});
const option = quote.options[0];
console.log(` quote_id=${quote.quote_id} ${option.total_price} ${option.currency}`);
console.log(` ${option.vehicle_class}`);
console.log("4. Read it back. Only the caller who created it can.");
const again = await call("GET", `/quotes/${quote.quote_id}`, undefined, {
authorization: `Bearer ${TOKEN}`,
});
if (again.quote_id !== quote.quote_id) throw new Error("quote id mismatch");
console.log(` expires_at=${again.expires_at}`);
console.log("Done. The quote is a 30-minute price snapshot, not a reservation.");
}
main();
What you have at the end
A 30-minute price snapshot. No vehicle is held, no driver is assigned, and nothing is booked. To turn it into a booking, hand the traveler to POST /checkout-sessions — they pay on Swiss Touristy, and your integration never touches a card.