speakerstrail API
Verified conference speaking history for European AI and tech events.
No authentication, no API key, no sign-up. Lists page at 25 by default and
100 at most; a larger limit is rejected rather than silently
truncated, so a caller never skips records without being told. There is no bulk-export
endpoint.
Everything under /api/v1/ reads. One endpoint writes:
POST /api/waitlist, which asks for a place in the free beta. It's rate
limited to 5 submissions per IP per
10 minutes, and both the success and the rejection
carry RateLimit-* headers.
Quick start
curl "https://speakerstrail.com/api/v1/stats"
curl "https://speakerstrail.com/api/v1/speakers?topic=llm&country=DE&spoke_after=2026-01-01"
curl "https://speakerstrail.com/api/v1/speakers/<slug>"
curl -X POST "https://speakerstrail.com/api/waitlist" \
-H "Content-Type: application/json" \
-d '{"email":"[email protected]","useCase":"Two AI keynotes in Munich next spring."}'
Every talk in every response carries source_url: the public conference
agenda the record was taken from. Fetch it and check us.
Endpoints
GET /api/v1/speakers
Returns speakers with their verified talks, most recently active first. Combine filters to build a shortlist: `topic` plus `country` plus `spoke_after` answers "who has spoken about LLMs on a German stage since January?" — the question this dataset exists for.
-
topic(query ) — Topic slug from the fixed vocabulary. See GET /api/v1/topics. Example:llm -
country(query ) — ISO 3166-1 alpha-2 country of the EVENT, not of the speaker. See GET /api/v1/countries. Example:DE -
event(query ) — Event slug. See GET /api/v1/events. Example:data-natives-2025 -
spoke_after(query ) — Only speakers whose most recent talk is on or after this ISO date. Example:2026-01-01 -
q(query ) — Case-insensitive substring match on the speaker name and their talk titles. Headlines are deliberately not searched — matching an employer name reads as a false positive. Example:kubernetes -
page(query ) — 1-indexed page number. Example:1 -
limit(query ) — Rows per page. Values above 100 are rejected rather than clamped, so a caller never silently skips records. There is no bulk-export endpoint. Example:25
Responses: 200, 400 · operationId listSpeakers
GET /api/v1/speakers/{slug}
Returns a single speaker with every talk we can evidence. Use this to verify a claim about a named person: each talk carries the agenda URL that proves it.
-
slug(path , required) — Speaker slug, lowercase and hyphenated. Example:jane-doe
Responses: 200, 404 · operationId getSpeaker
GET /api/v1/events
Returns the conferences the dataset was built from, most recent first, each with the agenda URL it was sourced from.
-
country(query ) — ISO 3166-1 alpha-2 country the event was held in. Example:DE -
page(query ) — 1-indexed page number. Example:1 -
limit(query ) — Rows per page. Values above 100 are rejected rather than clamped, so a caller never silently skips records. There is no bulk-export endpoint. Example:25
Responses: 200, 400 · operationId listEvents
GET /api/v1/events/{slug}
Returns a single event together with every speaker verified against its published agenda.
-
slug(path , required) — Event slug. Example:data-natives-2025
Responses: 200, 404 · operationId getEvent
GET /api/v1/topics
The fixed topic vocabulary with speaker counts. Only topics with at least three speakers are published, so every slug returned is a usable filter for GET /api/v1/speakers.
Responses: 200 · operationId listTopics
GET /api/v1/countries
Countries whose stages the directory can evidence, with speaker counts. These are event countries, never speaker nationalities.
Responses: 200 · operationId listCountries
GET /api/v1/cities
Cities whose stages the directory can evidence, with speaker and event counts. These are event cities, never where a speaker lives — the dataset holds no address data. Only cities with at least three verified speakers are published.
Responses: 200 · operationId listCities
GET /api/v1/stats
Counts for the whole dataset. Call this first to decide whether the directory covers your region and recency window before paging any list.
Responses: 200 · operationId getStats
POST /api/removal
Submits a GDPR removal or correction request for one profile. A removal takes the entry out of the directory and out of future rebuilds; a correction is checked against the event's own agenda first. Nothing is emailed back to the submitted address — an unauthenticated endpoint that mails whatever address it is handed is a spam relay — so the response body is the acknowledgement, and a human replies from a real mailbox within 7 days. This endpoint answers with the shared `/api/v1/*` error envelope rather than the flatter waitlist shape.
-
speakerName(body , required) — -
email(body , required) — Where the owner replies. Never published. -
profileUrl(body , required) — The directory page the request is about. -
reason(body ) — Optional.
Responses: 200, 400, 405, 500, 503 · operationId requestRemoval
POST /api/claim
Lets the subject of a profile tell us it is them, what they will speak on, and how an organizer may reach them. NOTHING PUBLISHES: there is no auth on this site, so a claim is a request that a human checks against the event's own agenda before any page changes — an endpoint that let anyone rewrite anyone's profile would be an impersonation tool, not a feature. `speakerSlug` must be a profile that exists; an unknown one and a malformed one return the same body on purpose, so this cannot be used to enumerate slugs. Nothing is emailed back to the submitted address, for the same reason the waitlist endpoint sends nothing. `contactable` is the only field that decides anything: false, and the contact link is never passed to an organizer.
-
speakerSlug(body , required) — The profile being claimed, e.g. "jane-doe". Must already exist. -
claimedName(body , required) — -
email(body , required) — Never published. Used to reply about the claim. -
topics(body ) — What they will speak on next — the one thing no agenda can tell us, since a page can only infer topics from talks already given. -
languages(body ) — -
contactUrl(body ) — A link an organizer can reach them through. Only shared if `contactable`. -
note(body ) — Anything wrong on the page, checked against the source. -
contactable(body ) — Explicit opt-in to having the contact link passed to organizers. Anything other than boolean true reads as false.
Responses: 200, 400, 405, 409, 429, 500, 503 · operationId claimSpeakerProfile
POST /api/waitlist
Adds an address to the beta waitlist. This is the only endpoint that writes, and the only thing on this site an agent can do on someone's behalf: the directory is free to read without it. Send `email` and, if you have them, `company`, `role` and `useCase` — the last one is what decides who is let in first, so a sentence about the event being planned is worth more than the other two together. Nothing is emailed back to the submitted address; an unauthenticated endpoint that mails whatever address it is handed is a spam relay. The reply is the response body, and a human follows up from there.
-
email(body , required) — Where we reply. Checked for shape only — an @ and a dot — and stored lowercased and trimmed. 320 characters is the RFC 5321 maximum address length. Example:[email protected] -
company(body ) — Who you work for. Optional; an empty value is stored as null, not as "". Example:Northwind Events -
role(body ) — What you do there. Optional. Example:Programme lead -
useCase(body ) — What you are trying to book, in your own words. Optional, and the field that decides who gets let in first. Example:Two AI keynotes for a 400-person conference in Munich next spring. -
sourceContext(body ) — Page attribution — which page the signup came from. Set automatically by the site; agents may pass it to indicate which surface referred the user. Example:Landing page: join (join)
Responses: 200, 400, 405, 409, 429, 500, 503 · operationId joinWaitlist
Errors
Every non-2xx response is JSON. There are no HTML error pages on any API path. There are,
honestly, two shapes rather than one — the read endpoints answer with a nested envelope,
and POST /api/waitlist with a flat one. Those waitlist bodies have been in
production since v0.10.0 and the sign-up form branches on them, so changing them to match
would break callers to tidy a document.
Reads under /api/v1/:
{
"error": {
"code": "invalid_parameter",
"message": "`limit` may not exceed 100; received 1000.",
"hint": "Request 100 at a time and page through with `page`.",
"documentation_url": "https://speakerstrail.com/docs/api"
}
} POST /api/waitlist:
{
"error": "You're already on the list! We review applications weekly and will reach out soon.",
"code": "duplicate_email"
} code is the stable value to branch on, in both shapes. The full enumeration
is in the specification.
Markdown instead of HTML
Every page on this site serves markdown to a client that asks for it, and every content
page has a .md twin at the same path.
curl -H "Accept: text/markdown" https://speakerstrail.com/
curl https://speakerstrail.com/speakers/<slug>.md
Responses carry Vary: Accept, so a cache never hands you the wrong
representation. An Accept header that admits neither markdown nor HTML gets
a 406 rather than a guess.
Using the data
The data is available for use with attribution to speakerstrail. What it is not is a
substitute for the source: if you republish a claim about a person, carry the
source_url with it. That link is the only reason the claim is worth
anything, and dropping it turns checkable evidence back into hearsay.
Anyone listed can ask to be removed at any time and we act on it, so a record present today may legitimately be gone next month. Re-fetch rather than caching indefinitely.