Skip to content
speakerstrail

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.

OpenAPI 3.1 specification llms.txt How the data is built

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.