SRG-SSR Hacker Guide

One product. Ten APIs. Five minutes to your first call.

SRG SSR is Switzerland’s public broadcaster — SRF, RTS, RSI, RTR and SWI. Behind it sits decades of video, audio and editorial text in four national languages, TV and radio schedules, votation results down to municipality level, the Swiss independent music scene, and SRF Meteo forecasts. Real, current, multilingual Swiss data — every endpoint here was verified on 7 September 2026.

Build something we couldn’t. And if you do — tell us. We’re looking for the projects that deserve to outlive the weekend.

Before you start — the terms

This product exists only for the Swiss {ai} Weeks hackathons, and it will be switched off afterwards. Our login credentials expire once the events are over. Therefore, don't rely on them for anything you plan to run on a long-term basis. However, feel free to contact me if you'd like to continue developing the application.

Nothing gets published without talking to us first. Use the data for your prototype and your demo on stage — that’s what it’s here for. But no public deployment, no dataset dumps, no republishing of our media or text. This is licensed broadcast content and we’re accountable for it.

That is not a brush-off. Come and ask, and we will usually find a way — including after the hackathon. What we can’t do is find out afterwards.

Also respect the flags the APIs give you: accessConditions on articles, playableAbroad on podcasts. They’re there for a reason.

Questions: api@srgssr.ch or grab us on site.

Get started in 5 minutes

1. Register → developer.srgssr.ch/en/user/register

2. Create an app → My Apps → ADD A NEW APP. Name it, leave the callback URL empty, → CREATE APP

3. Attach the product → select swiss-ai-weeks. One product, all ten APIs.

4. Copy CONSUMER KEY and CONSUMER SECRET from the app.

5. Token and first call

export SRG_KEY="your-consumer-key"
export SRG_SECRET="your-consumer-secret"

export SRG_TOKEN=$(curl -s -X POST \
  'https://api.srgssr.ch/oauth/v1/accesstoken?grant_type=client_credentials' \
  -u "$SRG_KEY:$SRG_SECRET" -H 'Content-Length: 0' | jq -r .access_token)

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/search/v2/media?q=klima' | jq '.total'

A five-digit number means you’re in.

6. Drop this client into your project and skip the plumbing

It fetches the token, caches it, and retries once if it expires mid-request. After that, one line per call.

import os, time, requests

HOST = "https://api.srgssr.ch"
_tok = {"value": None, "exp": 0}

def token() -> str:
    if _tok["value"] and time.time() < _tok["exp"]:
        return _tok["value"]
    r = requests.post(
        f"{HOST}/oauth/v1/accesstoken",
        params={"grant_type": "client_credentials"},
        auth=(os.environ["SRG_KEY"], os.environ["SRG_SECRET"]),
        headers={"Content-Length": "0"}, timeout=15,
    )
    r.raise_for_status()
    d = r.json()
    _tok["value"] = d["access_token"]
    _tok["exp"] = time.time() + int(d.get("expires_in", 3600)) - 60
    return _tok["value"]

def srg(path: str, **params):
    """srg('/search/v2/media', q='klima')"""
    def call():
        return requests.get(f"{HOST}{path}", params=params, timeout=30,
            headers={"Authorization": f"Bearer{token()}",
                     "Accept": "application/json"})
    r = call()
    if r.status_code == 401:      # token died early, retry once
        _tok["exp"] = 0
        r = call()
    r.raise_for_status()
    return r.json()

print(srg("/search/v2/media", q="klima")["total"])

In JavaScript the pattern is the same: Basic base64(key:secret) to get the token, cache it, Bearer on every call, retry once on 401.

Four rules that save an hour: always send Accept: application/json, some APIs default to XML · the host is always https://api.srgssr.ch · bu is required on video and audio (srf, rts, rsi, rtr, swi) · the token lasts 7 days — cache it, refresh on 401.

The APIs

All paths relative to https://api.srgssr.ch.

Search — find anything, then go get it

You get: metadata only. Per hit a playUrn, title and description per language, broadcastDate, type (AUDIO/VIDEO), topics, tags. No media files — take the URN to the video or audio API for playback URLs.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/search/v2/media?q=klima'

One keyword, 10’567 hits across German, French and Italian at once. Start here to find out whether the archive has what your idea needs.

Video metadata — the VOD catalogue

You get: metadata JSON, plus playback URLs on request. Shows, episodes, leads, long descriptions, images, topics, livestreams.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/videometadata/v2/latest_episodes?bu=srf'

# streaming resources for one video (bu is mandatory)
curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/videometadata/v2/{videoId}/mediaComposition?bu=srf'

Audio metadata — radio and podcasts

You get: metadata JSON, plus stream resources via /mediaComposition/audios/{audioId}. Also radio channels, shows, livestreams and channel song lists.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/audiometadata/v2/audios/search?bu=rts&q=intelligence%20artificielle'

Articles — full text, not just metadata

You get: the complete article body in content.text[], plus title, lead, kicker, keywords, contributors, genres, release and modification dates, related articles. SWI publishes in ten languages, so the same feed carries Spanish, Arabic, Japanese and more.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/srgssr-articles/v2/articles'

Paginate with the cursor from the response. The bu parameter currently does not filter.

This is the best plain-text corpus in the product: dated, sourced, editorially checked.

Play Subtitles — time-coded transcripts

You get: a URL to a WebVTT file. Download it and you have spoken Swiss German, French or Italian, transcribed and time-coded.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/srgssr-play-subtitles/v2/subtitles?episode=urn:srf:episode:tv:fb946f9c-3176-4eb0-a55a-9019245a7046'

EPG — what’s on air

You get: metadata JSON. Programme titles, short and long descriptions, start and end times, for any date.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/epg/v3/srf/radio/stations/srf-3'

Pattern: /epg/v3/{bu}/{tv|radio}/stations/{station}, optional ?date=YYYY-MM-DD. Only srf, rts and rsi.

TV
Radio
SRF
srf-1, srf-2, srf-info
srf-1, srf-2-kultur, srf-3, srf-4, srf-musikwelle, srf-virus
RTS
rts-1, rts-2
LA1ERE, ESPACE2, COULEUR3, OPTION_MUSIQUE
RSI
la-1, la-2
rete-uno, rete-due, rete-tre
⚠️ RTS radio names are UPPERCASE and case-sensitive — LA1ERE works, la1ere returns 400. Everything else is lowercase with hyphens. SRF TV channels are currently unreliable and may return 500. SRF radio, RTS and RSI are solid.

News Podcasts — actual audio files

You get: metadata and a direct MP3. Each item carries podcastHdUrl pointing at a downloadable file, plus duration, date, show, channel and playableAbroad.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/srgssr-news-podcasts/v1/srf/podcasts'

The last 12 hours of news, refreshed continuously.

Polis — how Switzerland actually voted

You get: structured result data. Votations and elections nested under cases, with parties, candidates, officials and 2’317 locations down to municipality level — including upcoming ballots.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/polis-api/v2/cases?lang=de'

lang is mandatory (de, fr, it). Use /cases as the entry point — /votations on its own returns an empty envelope. Also /locations, /elections, /parties, /candidates, /official.

Mx3 — the Swiss independent music scene

You get: metadata JSON, plus audio streams via /singles/{id}/token → /streams/{token}. Bands, singles, gigs, radios, playlists.

curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/mx3/v2/bands?query=basel'

SRF Meteo — forecasts for 100’000+ locations

You get: numeric forecast JSON. Five days at daily, 3-hourly and hourly resolution: temperature and perceived temperature, precipitation probability, wind and gusts, fresh snow, sunshine minutes, UV index, irradiance, humidity, pressure, sunrise and sunset.

# 1. resolve a coordinate to a geolocation id
curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/srf-meteo/v2/geolocations?latitude=47.3769&longitude=8.5417'

# 2. fetch the forecast
curl -s -H "Authorization: Bearer$SRG_TOKEN" -H 'Accept: application/json' \
  'https://api.srgssr.ch/srf-meteo/v2/forecastpoint/47.3797,8.5342'

Cache the response instead of polling — this is the most rate-sensitive service in the product.

Pick your challenge

There is no SRG SSR challenge this year. Our data is a building block for other people’s. Three fit exceptionally well.

Swiss Grounding MCP (Swisscom) — the strongest match. The failure you’re fixing is an LLM answering a Swiss question with German or Austrian facts. Polis gives you results per canton and municipality; Articles gives dated, sourced text in four languages; Search covers the archive. Wrap them as MCP tools and you have citable Swiss grounding.

Build a public AI service (Apertus / Public AI) — public-service data for public AI infrastructure. Articles and Play Subtitles across all business units give you a multilingual Swiss corpus, and RTR brings Romansh, one of the most under-served languages in any model.

Geneva, Education and Health tracks — time-coded native speech in four languages makes Play Subtitles and Audio a language-learning corpus you can’t buy. For health, Search and Articles open years of dated reporting.

For POs and UX people: you don’t need a token to start. Browse developer.srgssr.ch/en/api-catalog and shape the idea. The interesting question is rarely “can we get the data” — it’s “which Swiss question can only be answered with this data”.

When something breaks

  • 401 — token expired, or you sent the key instead of the token.
  • 403 — the swiss-ai-weeks product isn’t attached, or your app is still pending approval.
  • XML instead of JSON — you forgot Accept: application/json.
  • 400 — a missing required parameter: bu on video and audio, lang on Polis, q on search.
  • 500 on EPG SRF TV — known, use SRF radio, RTS or RSI meanwhile.
  • Search and Articles show as Private in the catalogue, so their doc pages may not open for you. The calls above work anyway.

If you hit a rate limit, come to us — don’t engineer around it. Some services sit on small packages, SRF Meteo in particular. We can raise them during the event. A team quietly throttling itself to 50 calls a day is a worse outcome than a two-minute conversation.