Real-Time News API Documentation for LLMs & Agents
Maintained by the oruk Engineering Team · Last updated: May 2026
oruk's REST and SSE API delivers corroborated breaking news from 240 live radio, TV, and structured feeds in 7 regions, with stories typically hitting the wire 30–90 seconds after they're spoken on air. The public feed is free; an account-bound key unlocks full filters, deeper history, the SSE stream, the MCP server, and webhooks. Most evaluation work fits inside the free tier (100 calls/month).
These docs are maintained by the oruk Engineering Team, which works across broadcast media ingestion, low-latency speech recognition, event extraction, and source corroboration. They describe the production API and the same live speech intelligence pipeline used on oruk.ai.
For sourcing rules, transcript handling, and quality controls, read the methodology. To learn who builds oruk, visit about oruk.
Runnable docs
Try the API with your key
Sign up for a free key and run real production calls from this page. The key is kept in this browser and sent only when you click a demo: REST examples go through a same-origin docs proxy that forwards your key to api.oruk.ai without storing it, while SSE examples connect directly to api.oruk.ai with ?api_key= because EventSource cannot set custom headers. Server examples should use X-API-Key.
Saving uses sessionStorage, so it survives refreshes in this tab and clears when the tab session ends. Avoid sharing screenshots that reveal your key.
Stories and filtersFresh stories, full-text search, impact/category/region filters, cursor pagination, CSV, and JSONL exports.
Story audit trailFetch a story by evt_... id with timeline, corroboration, source details, and quotes.
Live streamsTrader, agent trial, developer, and enterprise keys can use direct SSE for story and transcript updates; one open SSE connection counts as one API call.
AutomationUse the MCP server, webhooks on eligible keys, system stats, source catalogues, and region heatmaps.
Live outputReady
Sign up for a free key, then run an example. REST examples show status, timing, quota headers, and response data. SSE examples append live story or transcript events for streaming tiers.
How do I authenticate with the oruk API?
Send your API key in the X-API-Key header or Authorization: Bearer. Browser-only demos and SSE clients that cannot set headers can use the ?api_key= query param. Three read endpoints — /health, /v1/health, and /v1/stories/feed — are public and don't need a key; everything else under /v1/* does.
The full open-vs-keyed surface:
Endpoint
Auth
Notes
GET /health
PUBLIC
Liveness probe.
GET /v1/health
PUBLIC
Detailed health: streams, stories, uptime.
GET /v1/stories/feed
PUBLIC
The latest ~100 stories, no filters.
Everything else under /v1/*
REQUIRES KEY
Full filter surface, story lookups, sources, stats, SSE, webhooks, key management.
Pass your key via any of these:
Method
Header / Param
Example
API Key
X-API-Key
X-API-Key: ork_x9f2...
Bearer Token
Authorization
Authorization: Bearer ork_x9f2...
Query Param
api_key
?api_key=ork_x9f2... (for browser demos and SSE / EventSource clients that can't set headers)
Free includes 100 API calls per month and 1 key, with a 5-minute API delay. Pro includes 1,000 calls per month, 2 keys, and real-time API responses. Trader includes 10,000 calls per month, 2 keys, the real-time story and transcript SSE streams, and a 300 req/min rate limit. Developer keys are provisioned for API partners and agent integrations with real-time REST/SSE. Enterprise limits are customized per contract and can use per-minute or per-second windows.
How do I get started with the oruk API in 5 minutes?
Start with the public GET /v1/stories/feed?limit=10 endpoint, then sign up with email and generate an API key from the dashboard when you need authenticated story filters, full-text search with q, story detail lookup, sources, stats, SSE, or MCP access. The Free tier includes 100 keyed calls/month and 1 key — enough to verify the integration before picking a plan.
1. Create an account
Sign up at oruk.ai/signup with your email. You'll receive a 6-digit verification code to confirm your account.
2. Generate an API key
Head to the dashboard and click Generate Key. Free accounts get 1 key with 100 calls/month (5-minute API delay). Upgrade for higher quotas and real-time responses: Pro (1k/mo, real-time), Trader (10k/mo + SSE), or Enterprise (1M+/mo).
3. Pick a plan that fits your usage
Most evaluation work fits on Free. Upgrade from the pricing page when you need higher monthly call quotas, SSE stream access, or more keys.
4. Fetch live events
Pick your stack — the tab choice persists across every snippet on this page. We don't ship official SDKs yet; these are copy-paste templates against the public REST and SSE endpoints.
import httpx
r = httpx.get(
"https://api.oruk.ai/v1/stories/feed",
params={"limit": 10},
headers={"X-API-Key": "ork_xxxxxxxx"},
)
for story in r.json()["stories"]:
print(story["headline"])
SSE access by tier: Free, Pro, and Legacy do not include SSE (403). Trader ($49 first month, then $99/mo), Developer, and Enterprise stream in real time with the same wire as the public homepage — one connection, every new or corroborated story.
import httpx, json
with httpx.stream(
"GET",
"https://api.oruk.ai/v1/stream",
headers={"X-API-Key": "ork_xxxxxxxx"},
) as r:
for line in r.iter_lines():
if line.startswith("data:"):
event = json.loads(line[5:].strip())
print(event.get("headline") or event)
const es = new EventSource(
"https://api.oruk.ai/v1/stream?api_key=ork_xxxxxxxx"
);
es.addEventListener("story", (e) => {
const story = JSON.parse(e.data);
console.log(`[${story.urgency}] ${story.headline}`);
});
package main
import (
"bufio"
"fmt"
"net/http"
"strings"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.oruk.ai/v1/stream", nil)
req.Header.Set("X-API-Key", "ork_xxxxxxxx")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
s := bufio.NewScanner(resp.Body)
for s.Scan() {
line := s.Text()
if strings.HasPrefix(line, "data:") {
fmt.Println(strings.TrimSpace(line[5:]))
}
}
}
require "net/http"
uri = URI("https://api.oruk.ai/v1/stream")
req = Net::HTTP::Get.new(uri,
"X-API-Key" => "ork_xxxxxxxx",
"Accept" => "text/event-stream",
)
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |h|
h.request(req) do |resp|
resp.read_body { |chunk| puts chunk }
end
end
How do I fetch the latest news without an API key?
Call GET /v1/stories/feed — it's open to anyone and returns up to ~100 of the freshest stories with no filters required. Use it as your default endpoint for "what's new right now"; upgrade to /v1/stories with a key when you need filters, full-text search, or pagination.
GET/v1/stories/feedPUBLIC · powers the public wire on oruk.ai and the no-key fallback in oruk-mcp.
The feed returns up to ~100 of the freshest events. For older stories, use
/v1/stories?since=… with an API key.
How do I search and filter stories with full filters?
Call GET /v1/stories with an API key and any combination of category, q, since, region, country, urgency, min_impact, min_confidence, and topics. Results paginate with a stable cursor, and you can switch the response format to csv or jsonl for bulk export.
{
"stories": [{ /* same shape as /v1/stories/feed */ }],
"meta": {"count": 5, "cursor": "evt_7c2a1f", "hasMore": true}
}
How do I fetch a single story with its full timeline?
Call GET /v1/stories/{evt_id} with an API key. The response includes the full body, the developmental timeline, every independent corroborating source with the verbatim quote it used, the multi-category list, and event coordinates — everything you need to audit the story without trusting our judgement on its face.
How do I stream news events in real time over SSE?
Open a long-lived GET /v1/stream connection with your API key — it returns text/event-stream with three event types (story, corroboration, heartbeat) and stays open until your client disconnects. SSE is included on Trader ($49 first month, then $99/mo), Developer, and Enterprise. Free, Pro, and Legacy tiers receive HTTP 403 with an upgrade hint.
GET/v1/streamREQUIRES KEY
Event
Description
story
New story detected
corroboration
Existing story confirmed by additional source
heartbeat
System pulse for connection health
Optional stream filters
Leave filters out to receive the full live wire. Add one or more filters to receive only matching events. Repeated params and comma-separated values both work.
Parameter
Example
Matches
category
conflict
Primary event category
topics
oil,central bank
Any story topic overlap
region / regions
Europe
Event region or source region
sourceKey / sourceKeys
BBC,NPR
Source keys from /v1/sources
station / broadcast
BBC World Service
Station/source name, including corroborating sources
const es = new EventSource(
"https://api.oruk.ai/v1/stream?api_key=YOUR_KEY"
);
es.addEventListener("story", (e) => {
const story = JSON.parse(e.data);
console.log(`[${story.urgency}] ${story.body.slice(0, 100)}`);
});
Python
import httpx, json
with httpx.stream("GET", "https://api.oruk.ai/v1/stream",
headers={"X-API-Key": "YOUR_KEY"},
params={"category": "conflict", "regions": "Europe,Middle East"}) as r:
for line in r.iter_lines():
if line.startswith("data:"):
event = json.loads(line[5:].strip())
print(event)
How do I stream live transcript fragments over SSE?
Open GET /v1/transcripts/stream with a Trader, Developer, or Enterprise key to receive raw live transcript fragments from audio sources as they arrive. This is the lowest-level live speech surface: use it when you need every station-level update, then layer your own entity extraction, alerts, or matching logic on top.
GET/v1/transcripts/streamREQUIRES KEY
Parameter
Type
Description
sourceKey / sourceKeys
string
Optional source key filter, such as station keys returned by /v1/sources. Supports repeated params and comma-separated values.
station / broadcast
string
Optional station/source name filter. Partial station names are accepted for convenience.
region / regions
string
Optional source region or continent filter, such as Europe or North America.
city
string
Optional source city filter.
language
string
Optional source language filter, such as en, es, or fr.
sourceType
string
Optional source type filter. Use audio for radio/TV transcript fragments or text for structured sources.
Events
Event
Description
transcript
A live transcript segment with station/source metadata, sequence/timestamp fields, and text.
import httpx, json
with httpx.stream(
"GET",
"https://api.oruk.ai/v1/transcripts/stream",
params={"sourceType": "audio", "regions": "Europe,North America"},
headers={"X-API-Key": "ork_xxxxxxxx"},
) as r:
for line in r.iter_lines():
if line.startswith("data:"):
item = json.loads(line[5:].strip())
print(item.get("station"), item.get("text"))
const es = new EventSource(
"https://api.oruk.ai/v1/transcripts/stream?sourceType=audio®ions=Europe,North%20America&api_key=ork_xxxxxxxx"
);
es.addEventListener("transcript", (e) => {
const item = JSON.parse(e.data);
console.log(item.station || item.sourceName, item.text);
});
package main
import (
"bufio"
"fmt"
"net/http"
"strings"
)
func main() {
req, _ := http.NewRequest("GET", "https://api.oruk.ai/v1/transcripts/stream?sourceType=audio", nil)
req.Header.Set("X-API-Key", "ork_xxxxxxxx")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
s := bufio.NewScanner(resp.Body)
for s.Scan() {
if strings.HasPrefix(s.Text(), "data:") {
fmt.Println(strings.TrimSpace(s.Text()[5:]))
}
}
}
Call GET /v1/sources with an API key. The response is the full station catalogue across Europe, North America, Asia-Pacific, South America, Global, Middle East, and Africa, with city, region, country, language, default category, medium, live status, and polling cadence for each source.
GET/v1/sourcesREQUIRES KEY · same data renders on /sources.
Call GET /v1/stats with an API key for an instantaneous snapshot of system health, total stories, transcription volume, reconciliation latency, and the top categories by published volume. Use it as a heartbeat for monitoring or as a "health-of-the-wire" widget on your own dashboard.
How do I get geographic story counts for map overlays?
Call GET /v1/regions with an API key. The response is the story heatmap — an array of integer-bucketed lat/lon cells with the count of stories in each cell and the dominant category — currently ~1,800 cells globally, ready to drop into Leaflet, Mapbox, or any d3 choropleth.
Generate, label, and revoke normal account keys from the Dashboard — Free accounts get 1 key, Pro and Trader get 2 keys each, and Enterprise up to 100. Each key is independently revocable and bills against the same monthly quota; rotate one without affecting any others.
GET/v1/keysREQUIRES KEYDELETE/v1/keys/{key_id}POST/v1/keysadmin/provisioner only
GET /v1/keys returns metadata only: id, last four characters, label, tier, rate limit, monthly call limit, creation time, and expiration. It never returns an existing secret key. DELETE /v1/keys/{key_id} revokes a key you own. POST /v1/keys is reserved for admin/provisioner keys that mint Developer or 7-day agent-trial keys.
How do I send live story updates to my app with webhooks?
Developer, Enterprise, admin, and provisioner keys can subscribe HTTPS endpoints to story and corroboration events. Webhooks are useful when you want oruk to push high-impact updates into a newsroom bot, trading system, CRM, Slack bridge, or agent queue without holding an SSE connection open.
Only deliver matching story categories, such as conflict or economy.
min_impact
integer
Only deliver events with impact at or above this score.
min_confidence
number
Only deliver events at or above this confidence.
country
string
ISO country code, such as US or GB.
topic_match
string
Substring match against story topics.
Payloads are signed with X-Oruk-Signature: sha256=... using the webhook secret returned at creation time. Store that secret once; list responses do not expose it again.
How does the oruk MCP server work with Claude, Cursor, and Continue.dev?
Install the npm package oruk-mcp with one line — npx -y oruk-mcp — and any Model Context Protocol client (Claude Desktop, Cursor, Continue.dev) gets 12 tools, 6 resources, and 3 slash-prompts for searching corroborated real-time news. Without an API key it falls back to the public feed; set ORUK_API_KEY to lift it to the full filter surface, deeper history, and arbitrary evt_… story lookups.
The official production path today is stdio via npm:
npx -y oruk-mcp. Remote MCP over Streamable HTTP is planned as a
controlled beta after OAuth-compatible connector auth, quotas, origin controls,
and audit logging are in place.
How it fits
The MCP runs locally on your machine (spawned by your IDE). It talks directly to
api.oruk.ai on Railway — the same Python backend that powers
everything else. Azure (oruk.ai) is the public website only; there is no separate dataset
to query.
oruk://stories/latest — live snapshot of the freshest 25 stories
Prompts (slash commands)
/summarize_breaking — short briefing of urgent stories, optional category/region filter
/track_topic — chronological summary of a topic over the past N hours
/morning_briefing — structured "what happened overnight" rundown grouped by category
Without an API key
The public /v1/stories/feed endpoint is open. When ORUK_API_KEY
is unset, the server falls back to a 2-hour window of the freshest 50 stories on the feed
and applies all filters client-side. Good for almost every interactive query but trims
older stories and omits the sources / stats endpoints. Set the env var (free key from
signup or the dashboard) for the full
filter surface, deeper history, and arbitrary evt_… story lookups.
Quota accounting
Every MCP tool invocation that reaches the backend counts as one API call
against your key — the same quota the REST API uses (see
Rate limits & quotas). The MCP holds an in-process cache for ~3 s
on the public feed, so multi-tool turns inside one session usually collapse to a single
backend hit. If you run into 429 from a tool, the answer is the same as for the
REST API: upgrade or wait
out the month.
Hit the standard discovery URLs first — every public page links to them and they're explicitly allow-listed in robots.txt for AI-search crawlers. Each file gives an agent a different shape of context: a JSON capability manifest, an agent skill card, an operating guide, a Markdown sitemap, and curated/full-context LLM documentation.
/.well-known/ai.json — capability manifest for REST, SSE, webhooks, MCP, auth, story fields, and pricing status.
Does oruk support machine-payable autonomous agent calls?
Not yet — production billing is API-key based through human-managed Stripe subscriptions, and existing REST/SSE endpoints do not return live 402 Payment Required challenges. An opt-in x402 beta on new /v1/x402/* endpoints is in research; existing REST, SSE, webhooks, and MCP behaviour will not change when the beta lands.
Oruk is preparing autonomous payment support as a separate beta so machine clients
can pay for isolated live-data calls without navigating a human checkout page. The
first safe rollout target is new opt-in endpoints such as
/v1/x402/stories/feed and /v1/x402/stories/{id}, leaving existing
REST, SSE, webhooks, and MCP behavior unchanged.
Protocol
Status
Best fit
x402
Planned beta
Pay-per-request API calls, likely USDC on Base through a facilitator.
Stripe Machine Payments Protocol
Access-gated research
Session-based or spending-limit authorization once Stripe machine-payments access is available.
Stripe subscriptions
Live now
Human-managed recurring plans and API keys.
Until Oruk announces a beta, treat 401, 403, and 429 as the
active auth and quota signals. Machine-payment status is also published in
/.well-known/ai.json.
How are oruk API errors structured?
Every error response carries a stable machine-readable error code plus a human-readable message that may change. The HTTP status, the request ID echoed in x-request-id, and (on auth failures) a www-authenticate: Bearer header are everything an agent needs to recover or escalate without parsing English.
{
"error": "unauthorized",
"message": "Valid API key or JWT required."
}
The error field is a stable machine-readable code; the message field
is a human-readable description that may change. 401 responses also include a
www-authenticate: Bearer header, and the request id is exposed via
x-request-id on every response (useful when filing a support ticket).
Status
Code
When
400
invalid_email
Malformed email at signup
400
invalid_request
Malformed query parameters or body (e.g. since=yesterday)
401
unauthorized
Missing or invalid API key / JWT
401
invalid_code
Wrong email verification code
404
not_found
Resource not found (story id, source id, etc.)
409
email_taken
Email already registered
429
rate_limit_exceeded
Short-window rate limit exceeded; check Retry-After header (seconds to wait)
429
monthly_quota_exceeded
Monthly call quota exhausted for the key or account
How are oruk rate limits and monthly quotas enforced?
oruk enforces two limits on keyed traffic: a monthly call quota per API key and a short-window request rate. Standard public tiers use per-minute windows; provisioned Enterprise keys can use custom per-minute or per-second windows. Monthly counters reset on the 1st of each month UTC. The MCP server shares the same quota; the public /v1/stories/feed, /health, and /v1/health endpoints don't count toward any quota and don't need a key at all.
Tier
Calls / month
Short-window rate
Keys
SSE streams
free
100
30 req/min
1
Not included
pro
1,000
60 req/min
2
Not included
legacy
1,000
60 req/min
2
Not included
trader
10,000
300 req/min
2
Real-time
developer
10,000
300 req/min
Provisioned
Real-time
enterprise
1,000,000+
Custom
100
Real-time
Responses include X-RateLimit-Limit, X-RateLimit-Remaining, X-Oruk-Monthly-Limit, and X-Oruk-Monthly-Remaining. Newer custom Enterprise keys also include X-RateLimit-Window-Seconds so clients can tell whether a limit is per-minute or per-second.
What counts as one call?
Each REST request (GET, POST, DELETE) counts once at request-time.
An SSE connection counts once when the connection opens; events delivered while it stays open do not re-bill.
Each MCP tool invocation that reaches the backend counts as one call. Tools with structuredContent.mode: "public" use the same shared call against /v1/stories/feed.
The MCP holds an in-process cache for ~3 s on the public feed, so multi-tool turns inside one MCP session typically collapse to a single backend hit.
/health, /v1/health, and /v1/stories/feed are public; they don't require a key and don't count toward any quota.
What a 429 looks like
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{"error": "rate_limit_exceeded",
"retry_after_seconds": 60}
When the monthly quota is exhausted, the API returns monthly_quota_exceeded. Upgrade at /pricing to lift the quota,
or wait until the calendar month rolls over.
What broadcasters does oruk cover?
oruk covers monitored sources across major global regions, with per-source live status and polling cadence available on /v1/sources. The table below shows representative broadcasters, social feeds, and structured feeds by region.
Region
Coverage
Sample broadcasters
Europe
Regional
BBC World Service, LBC, France Info, Deutschlandfunk, NDR, RAI, SER, ERT, Radio Maryja, Times Radio
Sky News Arabia, Iran International, Radio Farda, Al Araby, Radio Orient
Africa
Regional
SAfm and regional broadcasters under expansion
Live counts come from /v1/sources; this table updates whenever the catalogue does. The full station list with city, region, language, and default category is on /sources.
What does each oruk plan include?
Four public tiers — Free, Pro ($12/mo), Trader ($49 first month, then $99/mo), Enterprise — plus provisioned Developer keys for API partners and agent integrations. The live wire on oruk.ai is real-time for everyone, no signup required. Paid and provisioned tiers add programmatic REST access; story and raw transcript SSE are available on Trader, Developer, and Enterprise.
Plan
Frontend
Programmatic Access
Free
Real-time wire
100 API calls/month, 1 key, REST, MCP; authenticated /v1/stories ~5 min behind the live wire; /v1/stream not included
Pro ($12/mo)
Real-time wire
1,000 API calls/month, 2 keys, real-time REST, MCP. SSE not included.
Trader ($49 first month, then $99/mo)
Real-time wire
10,000 API calls/month, 2 keys, real-time REST, story SSE, raw transcript SSE, MCP, webhooks.
Developer (provisioned)
Real-time wire
10,000 API calls/month, real-time REST, story SSE, raw transcript SSE, MCP, webhooks where enabled.
Enterprise
Real-time wire
1M+ API calls/month or custom quota, up to 100 keys, real-time story/transcript SSE, custom support
Have a referral code? Enter it at Stripe checkout for your first month of Pro free.
Sign Up
Create a free account to use the public wire and manage upgrades later. Free tier: 5-minute delayed frontend, 100 API calls/month, 1 key, no credit card required.
Available on every tier. Structured stories with topics, confidence, and location metadata.
SSE StreamGET /v1/stream
Trader and Enterprise tiers only: real-time story, corroboration, and heartbeat events. Free, Pro, and Legacy plans are not able to open this endpoint.
MCP Servernpx -y oruk-mcp
Drop into Claude Desktop, Cursor, or Continue.dev. 12 tools, 6 resources, 3 prompts. Free tier ready.
WebhooksPOST /v1/webhooks
HMAC-signed delivery to your endpoint. Up to 5 webhooks on Trader; filter by category, country, impact, and topic.
Plans
Free: 100 API calls/mo, 1 key, ~5 min API lag vs. the live wire. Paid self-serve plans start with 7 days free. Pro then runs $12/mo for real-time REST; Trader then runs $49 first month, then $99/mo for real-time REST + SSE + webhooks.
Trader carries the Developer surface with intro pricing. Enterprise raises limits and adds custom support. See pricing for the full breakdown.