Log In Sign Up

API Documentation

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).

https://api.oruk.ai
Checking API…

How do I authenticate with the oruk API?

Send your API key in the X-API-Key header (or Authorization: Bearer, or the ?api_key= query param for SSE clients that can't set headers). 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:

EndpointAuthNotes
GET /healthPUBLICLiveness probe.
GET /v1/healthPUBLICDetailed health: streams, stories, uptime.
GET /v1/stories/feedPUBLICThe latest ~100 stories, no filters.
Everything else under /v1/*REQUIRES KEYFull filter surface, story lookups, sources, stats, SSE, webhooks, key management.

Pass your key via any of these:

MethodHeader / ParamExample
API KeyX-API-KeyX-API-Key: ork_x9f2...
Bearer TokenAuthorizationAuthorization: Bearer ork_x9f2...
Query Paramapi_key?api_key=ork_x9f2... (only for 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 SSE stream, and a 300 req/min rate limit. Enterprise limits are customized.

How do I get started with the oruk API in 5 minutes?

Sign up with email, generate an API key from the dashboard, and call GET /v1/stories/feed?limit=10 with the X-API-Key header. The Free tier includes 100 calls/month and 1 key — enough to verify the integration before picking a plan.

1. Create an account

Sign up at oruk.ai 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.

Latest 10 stories from the public feed
curl -H "X-API-Key: ork_xxxxxxxx" \
  "https://api.oruk.ai/v1/stories/feed?limit=10"

5. Stream events live

SSE access by tier: Free, Pro, and Legacy do not include SSE (403). Trader ($49 first month, then $99/mo) and Enterprise stream in real time with the same wire as the public homepage — one connection, every new or corroborated story.

Subscribe to the SSE stream
curl -N -H "X-API-Key: ork_xxxxxxxx" \
  https://api.oruk.ai/v1/stream

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/feed PUBLIC  ·  powers the public wire on oruk.ai and the no-key fallback in oruk-mcp.

ParameterTypeDescription
limitintMax results (1-100, default 20)
sortstringrecent (default) or impact
since_hoursintWindow in hours, 1-168 (default 4)
curl "https://api.oruk.ai/v1/stories/feed?limit=10"

Response

{
  "stories": [{
    "id": "evt_3d22dc5b1d09",
    "headline": "Angola Quits OPEC After Six Decades",
    "summary": "Angola's oil minister announced...",
    "body": "Angola has formally exited OPEC...",
    "category": "economy",
    "categories": ["economy", "diplomacy"],
    "topics": ["OPEC", "oil", "Angola"],
    "urgency": "developing",
    "impact": 8,
    "confidence": 0.95,
    "sourceName": "BBC World Service",
    "eventCity": "Luanda", "eventCountry": "AO", "eventRegion": "Africa",
    "eventLat": -8.84, "eventLon": 13.23,
    "firstSeenAt": "2026-04-28T22:13:42Z",
    "updatedAt":   "2026-04-28T22:14:08Z",
    "corroboration": {"count": 3, "sources": ["BBC", "NPR", "RFI"],
                       "sourceDetails": [{"region":"Europe","language":"en","medium":"audio_radio"}, ...]},
    "timeline": [{"at":"...", "text":"..."}],
    "sources": [{"station":"BBC World Service", "quote":"Angola has formally..."}, ...]
  }],
  "meta": {"count": 10}
}

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.

GET/v1/stories REQUIRES KEY

ParameterTypeDescription
limitintMax results (1-100, default 20)
cursorstringStory id from a previous response
categorystringpolitics, conflict, economy, disaster, diplomacy, science, health, technology, culture, environment, sports, other
sinceISO 8601Only stories after this timestamp
topicsstringComma-separated topic filter
qstringFull-text search across headline / summary / body / source / city
regionstringNorth America, South America, Europe, Middle East, Africa, Asia-Pacific
countrystringISO 3166-1 alpha-2 (e.g. US, IL, DE)
urgencystringbreaking, developing, or routine
min_impactintLower bound on impact (0-10)
min_confidencefloatLower bound on confidence (0.0-1.0)
formatstringjson (default), csv, or jsonl for bulk export
curl -H "X-API-Key: ork_xxxx" \
  "https://api.oruk.ai/v1/stories?category=conflict&min_impact=7&limit=20"

Response

{
  "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.

GET/v1/stories/{id} REQUIRES KEY

curl -H "X-API-Key: ork_xxxx" \
  https://api.oruk.ai/v1/stories/evt_8f3a2b

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) and Enterprise. Free, Pro, and Legacy tiers receive HTTP 403 with an upgrade hint.

GET/v1/stream REQUIRES KEY

EventDescription
storyNew story detected
corroborationExisting story confirmed by additional source
heartbeatSystem pulse with active source count

Example output

event: story
data: {"id":"evt_9c4d1f","category":"conflict","confidence":0.96,"body":"..."}

event: corroboration
data: {"storyId":"evt_8f3a2b","count":5,"newSource":"NPR"}

event: heartbeat
data: {"ts":"2026-04-04T14:22:30Z","activeSourceCount":94}

JavaScript

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"}) as r:
    for line in r.iter_lines():
        if line.startswith("data:"):
            event = json.loads(line[5:].strip())
            print(event)

How do I list every monitored broadcast source?

Call GET /v1/sources with an API key. The response is the full station catalogue — currently 240 sources across Europe (116), North America (38), Asia-Pacific (36), South America (27), Global (12), Middle East (7), and Africa (4) — each with city, region, country, language, default category, medium, live status, and polling cadence.

GET/v1/sources REQUIRES KEY  ·  same data renders on /sources.

{"sources": [{
  "id": 1, "name": "BBC World Service", "city": "London",
  "region": "Europe", "lat": 51.51, "lon": -0.13,
  "defaultCategory": "politics", "status": "active",
  "lastSeenAt": "2026-04-04T14:22:00Z", "cadenceMs": 5000
}]}

How do I get system statistics from the oruk API?

Call GET /v1/stats with an API key for an instantaneous snapshot — at this writing the response shows 215 active sources, 42,000+ total stories, 670,000+ transcriptions processed, ~17 ms last reconciliation cycle, and the top-five categories by published volume. Use it as a heartbeat for monitoring or as a "health-of-the-wire" widget on your own dashboard.

GET/v1/stats REQUIRES KEY

{
  "activeSources": 94,
  "storiesTotal": 142,
  "geminiCycles": 87,
  "transcriptions": 52000,
  "topCategories": [{"category": "conflict", "count": 38}],
  "lastCycleMs": 4800,
  "uptimeSeconds": 3600
}

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.

GET/v1/regions REQUIRES KEY

{"regions": [
  {"lat": 52, "lon": 13, "count": 12, "topCategory": "politics"},
  {"lat": 36, "lon": 51, "count": 8, "topCategory": "conflict"}
]}

How do I manage my oruk API keys?

Generate, label, and revoke 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.

Send the key on every request:

curl -H "X-API-Key: ork_YOUR_KEY" https://api.oruk.ai/v1/stories

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.

  Claude / Cursor / Continue.dev
       │ stdio (JSON-RPC)
       ▼
  oruk-mcp (npx -y oruk-mcp)
       │ HTTPS
       ▼
  api.oruk.ai (Railway)         oruk.ai (Azure, PHP)
  • /v1/stories/feed  PUBLIC    • /docs, /feed/, /topic/
  • /v1/stories       AUTH      • /api/stories.php (browser proxy
  • /v1/stories/{id}  AUTH         to api.oruk.ai)
  • /v1/sources       AUTH      • /api/search.php
  • /v1/stats         AUTH      • RSS, Atom, sitemaps
  • /v1/regions       AUTH
  • /v1/stream  (SSE) AUTH

Two modes

Tools annotate structuredContent.mode as "public" or "authed" so the LLM (and you) know which path was taken.

ModeTriggered whenSearch corpusTools that work
public ORUK_API_KEY unset Last ~100 stories on /v1/stories/feed; filters applied client-side. oruk_get_latest, oruk_search_news, oruk_get_breaking, oruk_get_topic, oruk_get_story (recent only), oruk_get_corroboration, plus all static tools (oruk_list_categories, oruk_describe_api, oruk_show_pricing, oruk_health).
authed ORUK_API_KEY set Full corpus on Railway (~36 k stories at last check), with cursor pagination, full-text search, and arbitrary evt_… lookups. Everything in public mode plus oruk_list_sources, oruk_get_stats, deeper history, deeper filters.

Install

One command — no global install needed. Latest version published to npm at npmjs.com/package/oruk-mcp:

npx -y oruk-mcp

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %AppData%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "oruk": {
      "command": "npx",
      "args": ["-y", "oruk-mcp"],
      "env": { "ORUK_API_KEY": "ork_xxxxxxxxxxxx" }
    }
  }
}

Restart Claude Desktop. The oruk tools appear in the tool picker.

Cursor

Open Settings → MCP and add the same JSON block, or edit ~/.cursor/mcp.json directly.

{
  "oruk": {
    "command": "npx",
    "args": ["-y", "oruk-mcp"],
    "env": { "ORUK_API_KEY": "ork_xxxxxxxxxxxx" }
  }
}

Continue.dev (VS Code)

In ~/.continue/config.json:

{
  "mcpServers": [
    {
      "name": "oruk",
      "command": "npx",
      "args": ["-y", "oruk-mcp"],
      "env": { "ORUK_API_KEY": "ork_xxxxxxxxxxxx" }
    }
  ]
}

Tools exposed

ToolWhat it does
oruk_get_latestFreshest stories with optional category, region, country, urgency, impact, since filters.
oruk_search_newsFull-text search across recent stories.
oruk_get_breakingConvenience wrapper for high-urgency stories (urgency=breaking, impact ≥ 5).
oruk_get_storyFull detail of one story by evt_… id with timeline and verbatim source quotes.
oruk_get_topicStories in a single category, plus the editorial description of that category.
oruk_list_categoriesThe 12 oruk categories with one-line descriptions.
oruk_list_sourcesEvery monitored radio / TV / social / structured feed (requires API key).
oruk_get_statsLive system stats (requires API key).
oruk_get_corroborationIndependent sources and verbatim quotes for one story.
oruk_describe_apiFull REST/SSE reference, returned to the LLM for integration questions.
oruk_show_pricingTier comparison.
oruk_healthBackend health check.

Resources

The host can attach any of these oruk://… URIs to the model's context:

  • oruk://docs/quickstart — 60-second setup guide
  • oruk://docs/api-reference — concise REST + SSE reference
  • oruk://docs/methodology — pipeline, sourcing, quality controls
  • oruk://docs/categories — the 12-category taxonomy
  • oruk://docs/pricing — tier table
  • 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 at 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.

MCP package, support, and full reference: npmjs.com/package/oruk-mcp, llms-full.txt, and support@oruk.ai.

How can autonomous agents discover the oruk API?

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.

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.

ProtocolStatusBest fit
x402Planned betaPay-per-request API calls, likely USDC on Base through a facilitator.
Stripe Machine Payments ProtocolAccess-gated researchSession-based or spending-limit authorization once Stripe machine-payments access is available.
Stripe subscriptionsLive nowHuman-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).

StatusCodeWhen
400invalid_emailMalformed email at signup
400invalid_requestMalformed query parameters or body (e.g. since=yesterday)
401unauthorizedMissing or invalid API key / JWT
401invalid_codeWrong email verification code
404not_foundResource not found (story id, source id, etc.)
409email_takenEmail already registered
429rate_limit_exceededMonthly call quota exhausted; check Retry-After header (seconds to wait)
500internal_errorBackend hiccup; retry with exponential backoff
503service_unavailablePipeline temporarily down (rare); poll /health

How are oruk rate limits and monthly quotas enforced?

oruk meters by monthly call count per API key, not per second — calls 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.

TierCalls / monthPer-minute rateKeys/v1/stream
free 100 30 1 Not included
pro 1,000 60 2 Not included
legacy 1,000 60 2 Not included
trader 10,000 300 2 Real-time
enterprise1,000,000+ Custom 100 Real-time

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: 30
Content-Type: application/json

{"error": "rate_limit_exceeded",
 "message": "Monthly call quota exhausted on the free tier."}

Upgrade at /pricing to lift the quota, or wait until the calendar month rolls over.

What broadcasters does oruk cover?

240 monitored sources across 7 regions, with per-source live status and polling cadence available on /v1/sources. The breakdown below counts every catalogued broadcaster, social feed, and structured feed; Europe leads with 116 sources, followed by North America (38), Asia-Pacific (36), and South America (27).

RegionSourcesSample broadcasters
Europe116BBC World Service, LBC, France Info, Deutschlandfunk, NDR, RAI, SER, ERT, Radio Maryja, Times Radio
North America38NPR, KQED, WNYC, WAMU, WBUR, KDKA, KYW, ICI Radio-Canada, CBC, Bloomberg
Asia-Pacific36ABC Radio National, NHK World, YTN, CNR, CRI, VOV, MCOT FM, Tokyo FM
South America27Caracol, W Radio, Radio Bandeirantes, Radio CBN, Radio Globo, Rádio Observador, ADN
Global12BSKY (Bluesky), MSTDN (Mastodon), GDELT, COMMOD, CGECK, PREDT, USGS, NOAA, OpenFDA
Middle East7Sky News Arabia, Iran International, Radio Farda, Al Araby, Radio Orient
Africa4SAfm and three 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. The live wire on oruk.ai is real-time for everyone, no signup required. Paid tiers add programmatic REST access; SSE is available on Trader and Enterprise.

PlanFrontendProgrammatic Access
FreeReal-time wire100 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 wire1,000 API calls/month, 2 keys, real-time REST, MCP. SSE not included.
Trader ($49 first month, then $99/mo)Real-time wire10,000 API calls/month, 2 keys, real-time REST + SSE stream, MCP, webhooks.
EnterpriseReal-time wire1M+ API calls/month, up to 100 keys, real-time SSE, custom support

For Enterprise access, contact enterprise@oruk.ai.

Have a referral code? Enter it at Stripe checkout for your first month of Pro free.