One read-only feed: which news articles are relevant to which contracts, and why. Free and rate-limited. You hold a cursor and read forward, and corrections arrive on the same stream as the matches themselves.
This is a proposal, not a running service. The publication contract is not frozen and the API does not serve traffic yet, so treat every field and status below as open to change. If you would build against this, the useful thing to do now is argue with the shape of it — naming what is missing is worth more than agreement.
GET /v1/news?after=48217&limit=1 Host: api.throughlineapi.com Authorization: Bearer tl_...
{
"as_of": 48219,
"cursor": 48219,
"complete": true,
"matches": [
{
"match_id": "m_7f31c9",
"revision": 1,
"sequence": 48218,
"stage": "preliminary",
"novelty": "new",
"impact": 3,
"article": {
"report_id": "r_9a21c4",
"publisher": "reuters",
"url": "https://www.reuters.com/world/...",
"language": "en",
"title": "Israel and Lebanon agree a framework
to demarcate the land border"
},
"contract": {
"condition_id": "0x9f2c7a...e13b",
"question": "Israel-Lebanon border agreement
signed before 30 June 2027?",
"rule_revision": 3
},
"reason": "The framework names a demarcation
mechanism and a working timetable, which is
the step a signed agreement in the window
depends on.",
"uncertainty": "Framework texts have preceded
signature by more than a year before.",
"evidence": {
"availability": "full_text",
"refs": ["ev_3301", "ev_3302"]
},
"clocks": {
"source_published_at": "2026-11-03T08:14:00Z",
"received_at": "2026-11-03T08:16:41Z",
"assessed_at": "2026-11-03T08:17:09Z",
"public_at": "2026-11-03T08:17:52Z"
}
}
]
}
Every published revision gets a sequence, and it only ever
goes up. Keep the cursor from your last response, pass it back
as after, and you receive each revision exactly once, in order.
When complete is false there is more behind the
page, so call again immediately with the new cursor. When it is
true you are at the head and can back off.
Corrections arrive as revisions, not edits. A match that
changes stage, gains an impact score or gets withdrawn comes down the stream
as a new revision of the same match_id. Upsert on
match_id, keep the highest revision you have seen, and a
withdrawal is a revision with stage: "withdrawn" rather than a
deletion you have to infer from an absence.
This matters more than it sounds: a feed that only ever adds would let early, wrong matches sit in your copy forever. Reading the stream to the head is what keeps your copy honest, so a client that only reads new matches and never applies revisions is misusing the API.
Replaying a page you already processed is safe. Make your side idempotent
on (match_id, revision) and a retry costs nothing.
Four reads. There are no write endpoints, and no request causes any analysis to run — you are reading a feed that was produced whether you asked or not.
GET /v1/news
Matches at or after a cursor, oldest first, including revisions and withdrawals of matches you have already seen.
GET /v1/news/{match_id}
The current revision of one match, with its revision history.
GET /v1/markets/{condition_id}/news
Matches for one contract. The watchlist primitive: this is what an email subscription reads underneath.
GET /v1/coverage
What is covered and what is actually being screened right now. See below — this is the endpoint that makes a quiet feed interpretable.
| Name | Meaning |
|---|---|
| after | Cursor from your last response. Omit it and you start at the head, not at the beginning of time. |
| limit | Matches per page, 1 to 200. Defaults to 50. |
| condition_id | Restrict to specific contracts. Repeatable. |
| stage | preliminary, assessed, withdrawn. Repeatable. Filtering out withdrawals will leave stale matches in your copy. |
| novelty | new, update, repeat, uncertain. Repeatable. |
| min_impact | 0 to 4. Impact is a model assessment, not a probability — treat it as a sort key, not a threshold you trust. |
An empty result has four possible meanings and the matches feed cannot
tell them apart. /v1/coverage reports them separately for each
contract: whether it is in the declared scope, when it was last actually
screened, whether processing is delayed or unavailable, and whether it is
simply quiet.
A contract's last match is not its last screen. If you build an alerting product on this, read coverage as well as matches, or your users will read a broken pipeline as a calm week.
Declared coverage starts with geopolitical markets and is checked against what upstream selection really produces before launch. Reusing an existing analysis flow does not establish complete coverage of every market, and the endpoint will not claim otherwise.
A bearer token in the Authorization header. Keys are free
and read-only, because the whole API is read-only.
Exact rate limits are not set. They will be published as numbers rather than described as "generous", and they exist to bound operating cost, which is the constraint that keeps access free. If a limit would break a reasonable use of this, say which and why.
Optional email updates for contracts you follow will read this same feed. Cadence and provider are undecided.
Errors carry a stable machine-readable reason. The status says
whether to retry; the reason says why.
| Status | What happened |
|---|---|
| 400 | A parameter is malformed or out of range. The response names which one. Do not retry unchanged. |
| 401 | Missing or rejected key. |
| 404 | No such match or contract. A withdrawn match returns its final revision, not a 404. |
| 409 | Your cursor is ahead of the published head, so it did not come from us. Reset to the head. |
| 429 | Rate limited. Retry-After is always present and always honoured. |
| 503 | The feed is unavailable. This is never returned as an empty page of matches — an empty feed and a missing feed are different answers. |
The contract is still soft, which is the only time feedback is cheap to act on. If you run something that would consume this, the most useful reply names the contracts you want covered and the field you would need that is not listed above.