# Telltale Partner API — v1 For partner platforms that work with the same races we do. First partner: **Windshift** (windshift.org.nz, Joshua Groom). Written so a partner can build against it without asking us anything. **Base URL:** `https://telltaleracing.com` **Auth:** every request carries your key in a header — ``` X-Partner-Key: pk_live_xxxxxxxxxxxx ``` No key, a disabled key, or a call outside your scopes → `401` / `403` with `{"error": "..."}`. Every call is logged (key, endpoint, id, status, bytes) so both sides can see exactly what moved. **Limits:** per key, default 30 requests/minute and 2000/day. Over → `429` with `{"error":"rate limited","limit":"rate_per_min"}`. Ask if you need more; they're per-key settings. **Scopes** (set when the key is minted): `races:public`, `races:partners`, `races:*`, `ais`, `ingest`. --- ## What you can and can't get Each race carries a `share` value — `private`, `club`, `partners` or `public` — and your key sees a race only when its `share` is in your scopes. An admin can set it on any race, and what they set always wins. Where nobody has set it, a race that **any signed-out visitor can already watch on our own site** counts as `public`: if it is open to the whole internet, withholding it from you would be theatre. Everything else stays `private`, and restricted imports stay shut even when they carry a public flag — those came to us on someone else's terms. Private single-boat instrument logs are never shared at all. **Two separate consents, and a boat can pass one and fail the other.** *The race* is the club's record of a public event, consented at race level as above. *The track* is not always the club's to give: - **AIS we heard ourselves travels.** It was broadcast in the clear; anyone with a receiver in range heard the same thing. - **A phone or uploaded track needs that sailor's permission.** It is one person's own recording of their own boat, so it leaves only when its owner has switched sharing on. - **Aggregated AIS never travels**, whoever asks. AISHub and aisstream licence us to use their feed, not to pass it on, and an owner cannot consent on their behalf. Join AISHub yourself — you already have — and those vessels reach you from the source, on its own terms. A boat whose track is withheld still appears, with its identity and the club's result, marked `trackWithheld: true` — so you see a whole fleet and a deliberate choice rather than a hole, and can offer the sailor the switch. Instrument channels (heel, pitch, true/apparent wind, speed through water) are stripped separately from any boat whose owner hasn't opted in, marked `instrumentsRedacted: true`. What you get otherwise is positions, identity and the club's own result. **Instruments are the boat's, not the club's.** A race carries positions, identity and the club's official result — that is the club's record of a public event, and it travels whole. A boat's *instrument* channels (heel, true and apparent wind, speed through water) belong to its owner, so they are stripped unless that owner has switched partner sharing on for their boat. A boat whose instruments were withheld is marked `instrumentsRedacted: true`, so you can tell a deliberate choice from missing data, and offer the sailor the "share with Windshift" switch rather than assuming the boat has no instruments. AIS tracks are what **our own receivers** heard — `source: "telltale-shore"`, so you can label provenance honestly. There is no licence gate, no source quota and no per-request approval on them: what we heard ourselves, you can have, and re-share under `DATA-TERMS.md` rule 2 (credit us, mark it not for navigation). What we do **not** relay is the aggregated feed we buy in from aisstream and AISHub — not because we are precious about it, but because we were never granted a rebroadcast right to pass on. Join AISHub yourself (Windshift is) and that stream reaches you from its own source, on its own terms, with nothing lost in the middle. Please mark anything derived from them **not for navigation** — we do. **What you may re-share afterwards** is a separate question from what your scopes let you fetch, and it is answered once in `DATA-TERMS.md`: what we heard on our own receivers we can grant you outright (attributed, not for navigation); what came from another network carries that network's terms, named per record; what a contributor gave us stays theirs. --- ## Endpoints ### `GET /api/partner/v1/ping` Check the key. Returns your partner name and scopes. ```json { "partner": "Windshift", "scopes": ["races:public","races:partners","ais"] } ``` ### `GET /api/partner/v1/races` The races your key may see. Optional `?since=YYYY-MM-DD` and `?club=RPNYC`. ```json { "races": [ { "id": "race-20260802-rpnyc-winter-ais.json", "date": "2026-08-02", "name": "RPNYC Winter Series 2 Aug 2026", "club": "RPNYC", "event": "Winter Series", "source": "ais", "share": "partners", "boats": 7, "updated": "2026-08-02" } ], "next_cursor": null } ``` ### `GET /api/partner/v1/race/{id}` One race, whole fleet. `id` is the `id` from the list above. ```json { "name": "RPNYC Winter Series 2 Aug 2026", "club": "RPNYC", "source": "ais", "start_iso": "2026-08-02T12:55:00", "course_id": "rpnyc-w2", "boats": [ { "name": "Rattler", "mmsi": "512001234", "sail": "4321", "color": "#ff5252", "racing": true, "rating": 0.842, "official": { "system": "NHC", "handicap": 0.842, "elapsed": "1:24:00", "corrected": "1:10:43", "place": 1, "status": "", "bce": "0:00", "bce_s": 0 }, "track": [ { "t": 0.0, "lat": -41.2801, "lon": 174.8123, "sog": 6.2, "cog": 208 } ] } ], "_meta": { "_prov": "9f2c1ab4de77" } } ``` Notes on the shape: - `track[].t` is **seconds from `start_iso`**, not an absolute time. `sog` knots, `cog` degrees true. - `racing: false` means the boat was in the capture but not in the race (harbour traffic). Worth keeping out of a fleet comparison. - `official` is the club's own published result, not ours. `bce`/`bce_s` is "Behind Corrected Elapsed" — the elapsed time that boat needed to shave to tie the corrected-time winner (Sailwave's BCE). Published when the club publishes it, otherwise derived: time-on-time → `(corrected − winner) / TCF`. - `classes` appears when two divisions raced at once. - `_meta._prov` is a per-key provenance stamp. It identifies which key a file came from if it turns up somewhere unexpected. It isn't a restriction — it's how we can be relaxed about sharing. ### `GET /api/partner/v1/ais-tracks` Raw AIS our receivers captured. Needs the `ais` scope. `start` and `end` are required (ISO-8601 or epoch seconds, max 31 days apart), plus at least one of `bbox=latmin,lonmin,latmax,lonmax`, `mmsi=a,b,c`, or `fleet=`. ``` GET /api/partner/v1/ais-tracks?start=2026-08-02T12:00:00Z&end=2026-08-02T17:00:00Z&bbox=-41.36,174.74,-41.20,174.90 ``` ```json { "type": "ais-tracks", "start": "...", "end": "...", "bbox": "...", "boats": [ { "mmsi": "512001234", "name": "Rattler", "source": "telltale-shore", "fields": ["t","lat","lon","sog","cog"], "rows": [ ["2026-08-02T12:55:03Z", -41.2801, 174.8123, 6.2, 208] ] } ] } ``` Columnar to keep it small: `fields` names the columns, `rows` is values in that order. Times here are absolute ISO-8601 UTC (unlike a race's relative `t`). Capped at 5000 fixes per boat and 500 boats. ### `POST /api/partner/v1/ingest` Send us a race. Needs the `ingest` scope. Body is a race document (this shape, or Open Sail Data — see `OPEN-SAIL-DATA.md`). It lands **private** and is credited to you; a human decides whether it's published. ```json { "ok": true, "id": "in-windshift-1789512345.json" } ``` ### Push (us → you) If you give us an ingest URL and a key of yours, we POST each race to you the night it's saved — the same document as `race/{id}`, with your key in `X-Partner-Key`. That's the one worth having: you get every club race automatically instead of waiting for someone to upload one. --- ## The agreement, in plain words Neither of us scrapes the other; this API is the door, and it stays open. Each of us credits the other by name where the data shows up — "Joshua Groom / Windshift" on ours, "Telltale" on yours. Sailors can carry their own data in either direction, and neither of us treats that as leakage. Lanes: Telltale does capture, live, whole-fleet, results and club tools; Windshift does deep single-boat analysis and debrief. When someone needs the other thing, we send them to each other. Questions, more scope, a bigger rate limit, or a bug: just ask.