TowerTalk API
For flight schools, and the training-management software they use, to bring TowerTalk radio training results into their own records. Everything here is read-only: nothing you send can change a student's TowerTalk account.
The API is being opened to schools gradually. If your Academy does not show API & notifications yet, contact us.
Keys
A school's admin creates keys in Academy → API & notifications. Each key is shown once, when it is created; TowerTalk keeps only a one-way hash of it, so a lost key cannot be recovered, only replaced. A key reads that school's own students and nobody else's. Revoking a key stops it working on the very next request.
Send the key as a bearer token on every request:
curl https://towertalk.co.uk/api/v1/students \
-H "Authorization: Bearer ttk_live_YOUR_KEY"Endpoints
Four, all GET, all JSON except the PDF. A student is identified by their TowerTalk id, as listed by the first endpoint. A student who has left the school appears with "status": "former", and every endpoint returns their data only up to the day they left — nothing after. For a current student, the data starts when they linked to the school, unless they chose to share their earlier progress.
GET /api/v1/students
The school's linked students, current and former.
{
"data": [
{
"id": "6f1c2a7e-0b9d-4c55-9a51-3d2e8f7b1c40",
"name": "Amelia Pilot",
"status": "current",
"left_at": null,
"data_from": "2026-09-01T10:12:00Z",
"data_until": null
}
]
}GET /api/v1/students/{id}/progress
The summary and every exercise of the edition, attempted or not.
{
"data": {
"student": { "id": "6f1c2a7e-0b9d-4c55-9a51-3d2e8f7b1c40", "name": "Amelia Pilot", "status": "current", "left_at": null },
"edition": "uk",
"data_from": "2026-09-01T10:12:00Z",
"data_until": null,
"summary": {
"practice_seconds": 15240,
"graded_calls": 86,
"latest_readiness_check": { "date": "2026-10-02T18:40:00Z", "score": 72 },
"best_mock_exam": { "date": "2026-10-10T19:05:00Z", "score": 84 }
},
"exercises": [
{ "id": "A", "name": "Exercise A: Requests", "status": "attempted", "attempts": 7,
"best_score": 92, "latest_score": 88, "last_attempted": "2026-10-09T17:30:00Z" },
{ "id": "B", "name": "Exercise B: Readbacks", "status": "not_started", "attempts": 0,
"best_score": null, "latest_score": null, "last_attempted": null }
]
}
}GET /api/v1/students/{id}/results
Each mock exam and Readiness Check, newest first, with the three weakest areas found.
{
"data": {
"student": { "id": "6f1c2a7e-0b9d-4c55-9a51-3d2e8f7b1c40", "name": "Amelia Pilot", "status": "current", "left_at": null },
"edition": "uk",
"data_from": "2026-09-01T10:12:00Z",
"data_until": null,
"mock_exams": [
{ "date": "2026-10-10T19:05:00Z", "score": 84, "at_or_above_pass_mark": true,
"weakest_areas": ["Position reporting", "Frequency change", "Callsign use"] }
],
"readiness_checks": [
{ "date": "2026-10-02T18:40:00Z", "score": 72, "weakest_areas": ["Read back the pressure setting"] }
]
}
}GET /api/v1/students/{id}/record.pdf
The student's training record as an A4 PDF — the same document a school downloads from the Academy, with its own verification code. Anyone holding the PDF can check it at the address printed on it.
Errors and limits
Each key may make 120 requests a minute. Errors come back with an HTTP status and a body like this:
{ "error": { "code": "rate_limited", "message": "Too many requests for this key. Wait a minute and try again." } }401 missing_key— noAuthorizationheader.401 invalid_key— the key is wrong or has been revoked.404 student_not_found— no student with that id is linked to your school.404 not_available— the API is not switched on.429 rate_limited— with aRetry-Afterheader, in seconds.503 unavailable— try again shortly.
There are no write endpoints in version 1.
Notifications
A school can register one https address in Academy → API & notifications. TowerTalk then sends a POST there for each of these events about its linked students:
mock_exam.completedreadiness_check.completedexercise.completedstudent.joinedstudent.left
Each carries the event, the student's TowerTalk id, the school, the time and a short summary. Never a transcript, never audio.
POST /your/address
Content-Type: application/json
TowerTalk-Event: mock_exam.completed
TowerTalk-Delivery: 0d6c3f1e-7a2b-4c9d-8e5f-1a2b3c4d5e6f
TowerTalk-Signature: t=1760631900,v1=5f2b…
{
"id": "0d6c3f1e-7a2b-4c9d-8e5f-1a2b3c4d5e6f",
"event": "mock_exam.completed",
"created_at": "2026-10-16T16:25:00.000Z",
"school_id": "a3b9d0c4-5e61-4f7a-8b2c-9d0e1f2a3b4c",
"student_id": "6f1c2a7e-0b9d-4c55-9a51-3d2e8f7b1c40",
"summary": { "score": 84, "at_or_above_pass_mark": true, "edition": "uk",
"weakest_areas": ["Position reporting", "Frequency change", "Callsign use"] }
}Reply with any 2xx status to acknowledge. Anything else, or no answer within 10 seconds, is tried again after 1 min, 5 min, 15 min, 1 h, 3 h, 6 h, 12 h — the last attempt about 22 hours after the first — and then marked failed. Redirects are not followed. Every attempt shows in the school's delivery log. The same delivery id may arrive more than once, so treat it as the key for de-duplicating.
Checking a signature
Your secret is shown once, when you save the address (and again whenever you ask for a new one). The signature is an HMAC-SHA256, in hex, of the timestamp, a full stop, and the raw request body, made with that secret. Reject a notification whose timestamp is more than 5 minutes from your clock: that stops an old one being replayed. In Node.js:
import { createHmac, timingSafeEqual } from "node:crypto";
// rawBody: the request body exactly as received, before any JSON parsing.
export function isFromTowerTalk(secret, signatureHeader, rawBody) {
const parts = Object.fromEntries(signatureHeader.split(",").map((p) => p.split("=", 2)));
const t = Number(parts.t);
if (!Number.isInteger(t) || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
const given = Buffer.from(parts.v1 ?? "", "hex");
return given.length === expected.length && timingSafeEqual(given, expected);
}Exercise links
Every exercise, scenario, the mock exam and the Readiness Check has a permanent address, such as /go/exercise/a, /go/scenario/engine-failure, /go/mock-exam and /go/readiness-check, for putting TowerTalk straight into a syllabus. If something is ever renamed, the old address keeps working. Add ?school=your-school to see which of your students arrived through your syllabus; nothing else belongs in the address. A school's admin can download the full list as CSV from Academy → Links for your syllabus.
TowerTalk is a training aid. Its results are not an examiner's assessment.
