Skip to content
API

Read it from your code.

A read API and an MCP server for AI assistants. Both need a key.

The schema

Generated from the API, so always current. None of these needs a key.

Keys

Send your key in the X-API-Key header. A missing or wrong key gets a 403 with this body:

{"detail": "Invalid or missing X-API-Key header"}

These answer without a key: /health, /health/worker, /docs, /redoc, /openapi.json.

Responses are JSON. Each is cached for the time shown on its endpoint, separately for each key.

Getting a key

Keys come with a Desync Starter account. Write to us or book a call and we will set one up.

Use it with an AI assistant

Copy the whole reference as plain text, paste it into Claude, ChatGPT or whatever you use, and ask it to write the call you need.

Endpoints

GET /api/search

Search items by words and by meaning: a keyword ranking (BM25) and a vector ranking fused by Reciprocal Rank Fusion. With no q, it lists items most on topic first. Results stop after 200.

Parameters

q
string, optional; empty lists by relevance
tags
string, repeatable; a topic slug from /api/tags
source_type
string, repeatable; rss, web, youtube, podcast or arxiv; only applies when q is set
from_date, to_date
ISO date or datetime; the published window
language
string, repeatable; ISO code of the original language
content_type
string, repeatable; article, video, podcast_episode, audio or paper
min_relevance
number 0 to 1; only items scored at least this on topic
alpha
number 0 to 1, default 0.5; 0 ranks by words only, 1 by meaning only
limit
integer 1 to 100, default 10
offset
integer, default 0
original
true for the original-language text instead of English; default false

Response

SearchResponse { query, total_estimated, limit, offset, hits: [SearchHit] }
SearchHit: ArticleSummary + match_score, rrf_score, matched_chunk_text, bm25_rank, cosine_rank
ArticleSummary: id, title, summary, relevance_score, published_at, language, source_id, source_name, content_type, external_url, is_translated

Cache-Control public, max-age=300

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/search?q=artificial%20intelligence&min_relevance=0.7&limit=5"

GET /api/latest

The newest listed items, with the entities and topics on each.

Parameters

limit
integer 1 to 100, default 25
min_relevance
number 0 to 1; unset includes items not yet scored
original
true for the original-language text instead of English; default false

Response

LatestResponse { generated_at, items: [LatestItem] }
LatestItem: ArticleSummary + fetched_at, entities, tags

Cache-Control public, max-age=120

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/latest?limit=10&min_relevance=0.5"

GET /api/articles/{id}

One item in full: its text, sections, images, transcript, entities, topics and the five nearest related items. Lines the source repeats in every item (newsletter and donation pleas, share bars, copyright notices) are left out of the text, and boilerplate_removed says how many.

Parameters

id
integer, in the path
original
true for the original-language text instead of English; default false

Response

ArticleDetail: ArticleSummary + content_text, content_html, content_clean_html, body_format, sections, images, lead_image_url, authors, transcript, transcript_summary, transcript_summary_excerpted, transcript_topics, media_url, embedded_video_url, yt_description, original_links, hidden_reason, fetched_at, metadata, entities, tags, related_articles, boilerplate_removed

Cache-Control public, max-age=3600

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/articles/61736"

GET /api/articles/{id}/context

Who wrote an item, who is in it and what it says they did: the byline (authors matched to people, with their roles, and the outlet), each person named with up to two organisations and two places, the relations the item states, each with its sentiment (negative, neutral, positive or mixed), and the item's own sentiment. Listed items only.

Parameters

id
integer, in the path

Response

ArticleContextResponse { byline: { authors: [{ name, entity, roles: [{ predicate, other, count }] }], outlet: { id, name, homepage, country, language }, outlet_is_author }, people: [{ id, name, entity_type, orgs: [{ id, name, entity_type, predicate, count }], places }], events: [{ subject, predicate, object, kind, detail, sentiment, confidence }], sentiment: { label, score, model } or null }

Cache-Control public, max-age=3600

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/articles/61736/context"

GET /api/tags

The topics, with how many items carry each.

Parameters

limit
integer 1 to 200, default 50
offset
integer, default 0

Response

TagsResponse { tags: [{ id, slug, name, article_count }], total }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/tags?limit=50"

GET /api/entities

People, organizations, places and more, merged duplicates folded in, most mentioned first.

Parameters

type
string, repeatable; person, organization, project, document, event, location, concept, university or journal
q
string; part of a name
min_articles
integer, default 1
limit
integer 1 to 200, default 50
offset
integer, default 0

Response

EntitiesResponse { entities: [{ id, name, entity_type, description, article_count }], total, limit, offset }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/entities?type=person&limit=10"

GET /api/entities/{id}

One entity and the items that name it, newest first. A merged duplicate's id answers with the entity it was merged into.

Parameters

id
integer, in the path
from_date
ISO date or datetime; only items published since
limit
integer 1 to 100, default 20
offset
integer 0 to 10000, default 0
original
true for the original-language text instead of English; default false

Response

EntityArticlesResponse { entity: { id, name, entity_type, description, article_count }, articles: [ArticleSummary], total, limit, offset }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/entities/9?limit=5"

GET /api/graph

The nodes around one item or entity, one step out: an item's entities, or the entities an entity appears with, heaviest first.

Parameters

center_id
string, required; article:N or entity:N
depth
integer 1 to 3, default 1; only one step is computed today
limit
integer 1 to 500, default 100
include_relations
true to add an entity's typed relations, subject to object with a predicate; default false

Response

GraphResponse { center, depth, nodes: [{ id, type, label, metadata }], edges: [{ source, target, type, weight, predicate }] }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/graph?center_id=entity:9&limit=24&include_relations=true"

GET /api/graph/path

The shortest chain of entities that appear together, from one entity to another. With no chain, both ends come back and no edges.

Parameters

from_entity_id
integer, required
to_entity_id
integer, required
max_hops
integer 1 to 4, default 4

Response

GraphResponse { center, depth, nodes: [{ id, type, label, metadata }], edges: [{ source, target, type, weight, predicate }] }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/graph/path?from_entity_id=9&to_entity_id=12"

GET /api/graph/transitive

Every entity within a few steps of one entity, through the entities they appear with, and the edges among them.

Parameters

center_id
string, required; entity:N
depth
integer 1 to 3, default 2
limit
integer 1 to 500, default 120
include_relations
true to add the typed relations; default false

Response

GraphResponse { center, depth, nodes: [{ id, type, label, metadata }], edges: [{ source, target, type, weight, predicate }] }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/graph/transitive?center_id=entity:9&depth=2&limit=60"

GET /api/sources

The sources we read, with item counts, when each was last read and how many reads in a row failed.

Parameters

source_type
string, repeatable; rss, web, youtube, podcast or arxiv
limit
integer 1 to 500, default 100
offset
integer, default 0

Response

SourcesResponse { sources: [{ id, name, url, source_type, is_active, article_count, last_fetched_at, consecutive_failures }], total }

Cache-Control public, max-age=300

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/sources?limit=500"

GET /api/stats

The archive's counts: items, on topic, passages, entities and merged duplicates, topics, sources, languages, the newest item on topic and the last pipeline run.

Response

StatsResponse { total_articles, total_classified, total_relevant, total_chunks, canonical_entities, resolved_duplicates, controlled_tags, sources_active, sources_failing, sources_stalled, newest_relevant_at, languages, last_pipeline_run }

Cache-Control public, max-age=300

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/stats"

GET /api/insights

The pipeline's state and the last weeks in numbers: freshness, coverage, items each week, sources, languages, kinds, and who and what was named most in 30 days.

Response

InsightsResponse { generated_at, freshness, coverage, weekly, sources, top_sources, languages, content_types, entity_types, top_entities_30d, top_tags_30d }

Cache-Control public, max-age=600

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/insights"

GET /api/analytics

Monthly series: topics and entities by month, topics found and assigned, the fastest-rising entities, transcripts and translation.

Response

AnalyticsResponse { generated_at, corpus, tag_discovery, tag_assignments, top_topics_by_month, top_entities_by_month, fastest_growing_entities, caveats }

Cache-Control public, max-age=900

bash · Example
curl -H "X-API-Key: $OBSERVATORY_API_KEY" \
  "https://api.observatory.desync.ai/api/analytics"

GET /health

Whether the API is up. No key needed.

Response

{ "status": "ok" | "degraded", "db_ok": bool, "embedder_ok": bool }

Cache-Control Not cached

bash · Example
curl "https://api.observatory.desync.ai/health"
MCP for AI assistants

The same data as read-only tools, over streamable HTTP. It needs a key, sent in the same X-API-Key header as the API (put yours in place of YOUR_KEY below), and is rate-limited like it.

MCP https://api.observatory.desync.ai/mcp

Tools

search_articles
Search by words and meaning; titles and summaries in English. Args: query, tag (a topic slug, optional), limit (up to 25).
get_article
One item's English text and details by id; the body is cut at about 8,000 characters, with a flag when it is. Args: article_id.
list_entities
People, organizations, projects and more, most mentioned first. Args: query (optional), limit (up to 50).
entity_graph
Who a named entity appears with and how they relate, as nodes and edges. Args: entity, depth (up to 2).
list_sources
The active sources, with item counts. No args.
get_stats
The archive's counts: items, entities, passages, topics, sources, languages. No args.

Claude Code

bash
claude mcp add --transport http desync-observatory https://api.observatory.desync.ai/mcp --header "X-API-Key: YOUR_KEY"