Start

Quickstart

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.

Before you start

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

For 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=...

curl

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

python

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

typescript

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.