Reference

Endpoint reference

Every operation on /api/v1. The conventions they all share — slugs, the list envelope, error codes — are in REST API.

This page is generated from openapi.json, which is checked against the live routes on every build. If an endpoint is here, it exists; if it exists, it is here.

Posts

The corpus itself: newsletter issues, podcast episodes, blog posts.

get /api/v1/posts

Browse posts, newest first

A chronological feed. Give at most one scope — collection, source, person, topic or sponsor — to narrow it; they are alternative ways of cutting the same list, not filters that compose, so passing two is a 422 with code: scope_conflict.

For relevance-ranked search, use GET /api/v1/posts/search.

Parameters

NameInTypeNotes
collection query string Collection slug. Restricts to that collection's publications.
source query string Publication slug.
person query string Person slug. Posts they are credited on.
topic query string Topic slug.
sponsor query string Sponsor slug. Posts that read the brand out.
window query all | 24h | 48h | week | month | 3months | year A rolling window measured back from now, not a calendar period.
include_dead_sources query boolean Include posts from publications we have marked dead (no longer publishing). Archived publications stay excluded either way.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of posts.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/posts/search

Full-text search

Ranked by relevance by default, or by recency for a chronological stream of mentions.

Bare space-separated terms are ANDed, and may match anywhere in a document — on a two-hour transcript, comfort food matches an episode saying "comfort" in the intro and "food" an hour later. Quote a phrase you mean as a phrase. Quoted phrases match an exact word sequence; uppercase AND/OR/NOT combine terms; field:value scopes to title, body, transcript or author. Malformed syntax is repaired rather than rejected.

There is deliberately **no person filter** here. We credit a person on far too few posts for it to be a filter worth trusting, and a keyword search for the name finds more. Chronological browse still takes person, because that reads the credits directly rather than the index.

Parameters

NameInTypeNotes
q req query string What to search for.
sort query relevance | recency Result ordering.
collection query string Collection slug. Pass at most one of collection or source.
source query string Publication slug.
window query all | 24h | 48h | week | month | 3months | year A rolling window measured back from now.
include_dead_sources query boolean Include posts from publications marked dead.
source_type query string One or more of podcast, newsletter, blog, comma-separated and OR'd. What the publication *is*.
publishing_platform query string One or more of rss, substack, beehiiv, bluesky, email, comma-separated and OR'd. Where it is *hosted*. Independent of source_type: a Substack-hosted podcast is both.
language query string Two-letter language code, e.g. en.
has_transcript query boolean Omit for no filter.
has_audio query boolean Omit for no filter.
min_word_count query integer Inclusive.
min_duration_seconds query integer Inclusive. Seconds, so 1200 is twenty minutes.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of results, each with a highlighted snippet.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/posts/{post}

One post

The full record, including body text where we hold it.

Parameters

NameInTypeNotes
post req path string The post slug.

Responses

200 The post.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/posts/{post}/transcript

A podcast episode as speaker turns

Paged speaker turns, with real names substituted for "Speaker 1" wherever we could identify them. This is the only way to read a transcript — the words are never inlined on the post itself.

transcript_status rides alongside the envelope so a caller that gets zero turns can tell "not transcribed yet" from "transcribed, but empty".

Parameters

NameInTypeNotes
post req path string The post slug.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of speaker turns.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

People

Authors, hosts and guests credited on posts.

get /api/v1/people

Find a person by name or handle

Parameters

NameInTypeNotes
q req query string A name or handle.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of people.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/people/{person}

One person

Adds what they have been talking about lately and where they appear. topics is empty for anyone whose posts predate topic extraction.

Parameters

NameInTypeNotes
person req path string The person slug.

Responses

200 The person.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Sources

The publications we index.

get /api/v1/sources

The publications we index

With no q, the browsable list of everything we index.

Parameters

NameInTypeNotes
q query string Optional name filter.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of publications.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/sources/{source}

One publication

Adds what it has been covering lately and which brands have advertised on it.

Parameters

NameInTypeNotes
source req path string The publication slug.

Responses

200 The publication.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Topics

What the corpus is about, extracted off posts.

get /api/v1/topics

What the corpus is about

With q, all-time matches for that name; without it, the most-covered topics of the last 90 days.

**This index is capped, not paged.** It returns at most 100 rows, next_cursor is always null, and total goes null when the cap was hit. Passing cursor is a 422 — narrow with q instead. Past the first hundred, a ranked list of a million topics is not something anyone reads, and the query stops being cheap.

Parameters

NameInTypeNotes
q query string Optional name filter, matched anywhere in the name.
limit query integer How many rows. Honoured up to the cap.

Responses

200 Up to 100 topics.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/topics/{topic}

One topic

Adds the publications covering it most and the people credited on its posts. For the posts themselves, use GET /api/v1/posts?topic=<slug>.

Parameters

NameInTypeNotes
topic req path string The topic slug. Names are display text; do not pass one as a key.

Responses

200 The topic.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Sponsors

Brands read out as sponsors. Ads that ran, not deals a show signed.

get /api/v1/sponsors

Brands read out as sponsors

With q, all-time matches for that brand name, matched case-insensitively; without it, the most-heard sponsors of the last 90 days.

Capped rather than paged, exactly like topics.

**Read these as ads that ran, not deals a show signed.** Programmatic insertion puts spots into feeds the publisher never sold — a physics channel's transcript reads out ads a streaming platform placed, not sponsors it found.

Parameters

NameInTypeNotes
q query string Optional brand-name filter.
limit query integer How many rows. Honoured up to the cap.

Responses

200 Up to 100 sponsors.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/sponsors/{sponsor}

One sponsor

Adds the shows the brand has been heard on, most appearances first.

Parameters

NameInTypeNotes
sponsor req path string The sponsor slug.

Responses

200 The sponsor.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Collections

Named sets of publications, used to scope a search.

get /api/v1/collections

Your team's collections, and ours

Both the collections your team owns and the global ones Sputnik curates. is_global tells them apart; global ones are readable by everyone and editable by no one.

Parameters

NameInTypeNotes
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of collections.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/collections

Create a collection

Created empty. Add publications with POST /collections/{collection}/sources.

Body

FieldTypeNotes
name req string
description string?

Responses

201 The new collection.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/collections/{collection}

One collection, with its publications

Parameters

NameInTypeNotes
collection req path string The collection slug.

Responses

200 The collection.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

patch /api/v1/collections/{collection}

Rename a collection

Only the given fields change. Which publications are in it is a separate thing.

Parameters

NameInTypeNotes
collection req path string The collection slug.

Body

FieldTypeNotes
name string
description string?

Responses

200 The updated collection.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/collections/{collection}

Delete a collection

Removes the set, not the publications in it.

Parameters

NameInTypeNotes
collection req path string The collection slug.

Responses

204 Deleted.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/collections/{collection}/sources

Add publications to a collection

Adding one already in it is a no-op, so this is safe to retry.

Parameters

NameInTypeNotes
collection req path string The collection slug.

Body

FieldTypeNotes
source_slugs req array Publication slugs, from search_sources.

Responses

200 The collection, with its new count.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/collections/{collection}/sources/{source}

Remove a publication from a collection

Parameters

NameInTypeNotes
collection req path string The collection slug.
source req path string The publication slug.

Responses

204 Removed.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Projects

One thing your team monitors for. Alerts live inside a project.

get /api/v1/projects

What your team monitors for

A project is usually one of your own customers. Archived ones are hidden unless you ask for them.

Parameters

NameInTypeNotes
include_archived query boolean Include archived projects.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of projects.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/projects

Create a project

Responses

201 The new project.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/projects/{project}

One project

Parameters

NameInTypeNotes
project req path string The project slug.

Responses

200 The project.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

patch /api/v1/projects/{project}

Update or archive a project

Setting is_active to false archives it and switches off every alert in it. Setting it back to true restores the project but leaves those alerts off — turning monitoring back on should be a decision, not a side effect.

Parameters

NameInTypeNotes
project req path string The project slug.

Responses

200 The updated project.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/projects/{project}

Delete a project

Takes its alerts and their hits with it. Archive instead if you want the history.

Parameters

NameInTypeNotes
project req path string The project slug.

Responses

204 Deleted.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Alerts

Standing searches that tell you when something new matches.

get /api/v1/alerts

Your team's alerts

Parameters

NameInTypeNotes
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of alerts.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/alerts

Create an alert

An alert belongs to a project and holds up to ten saved searches; a post hits the alert if any of them matches.

search is a convenience that creates the alert's first saved search in the same call, so "watch for this" is one request. It is nested because an alert and its search both have a name — search.q and search.name are exactly what the searches endpoint takes.

A new alert delivers nowhere until you give it a destination. That state is legal, and the app flags it.

Body

FieldTypeNotes
name req string Shown in notifications, e.g. "Competitor: Acme".
project req string Project slug. An archived project, or one belonging to another team, is a 422.
is_active boolean
search object Optional first saved search.

Responses

201 The new alert.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/alerts/{alert}

One alert

With its searches, its destinations and its hit count. The hits themselves come from GET /alert-hits.

Parameters

NameInTypeNotes
alert req path string The alert slug.

Responses

200 The alert.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

patch /api/v1/alerts/{alert}

Update an alert

Changes only what is true of the notification — its name, its project, whether it is on. What it looks for lives on its searches; where it delivers on its destinations.

Parameters

NameInTypeNotes
alert req path string The alert slug.

Body

FieldTypeNotes
name string
project string Move the alert to another project.
is_active boolean

Responses

200 The updated alert.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/alerts/{alert}

Delete an alert

Takes its hits with it. Set is_active to false instead to stop it matching while keeping the history.

Parameters

NameInTypeNotes
alert req path string The alert slug.

Responses

204 Deleted.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/alerts/{alert}/searches

What an alert looks for

Parameters

NameInTypeNotes
alert req path string The alert slug.

Responses

200 The alert's saved searches. Small and unpaged, but the envelope is the same.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/alerts/{alert}/searches

Add a saved search

Takes q and the same filter names GET /posts/search takes, so a search that worked lifts straight into an alert without renaming anything.

A search added today starts finding hits on the next run, which looks back a day. It does not reach further back: an alert reports new things, and the history of a query belongs in search.

An alert holds ten searches; the eleventh is a 422.

Parameters

NameInTypeNotes
alert req path string The alert slug.

Responses

201 The new saved search.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

patch /api/v1/alerts/{alert}/searches/{search}

Edit a saved search

Applies from the next run onwards. It does not re-run the new definition over the past — that would report old posts as news.

Parameters

NameInTypeNotes
alert req path string The alert slug.
search req path integer The search id, from the list endpoint. Resolved through its alert, so another team's id is a 404.

Responses

200 The updated search.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/alerts/{alert}/searches/{search}

Switch a saved search off

Deactivates rather than deletes: hits exist because this search found them, and removing it outright orphans that explanation. PATCH is_active back to true to restore it.

Parameters

NameInTypeNotes
alert req path string The alert slug.
search req path integer The search id.

Responses

204 Deactivated.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/alerts/{alert}/destinations

Where an alert delivers

Parameters

NameInTypeNotes
alert req path string The alert slug.

Responses

200 Its destinations.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/alerts/{alert}/destinations

Point an alert somewhere

Give exactly one of email or webhook. The channel comes from the webhook rather than being passed in — the two must agree, and this is the only way to guarantee it.

Adding a destination does not replay history: hits already delivered stay delivered, so connecting a webhook in week three does not fire hundreds of events about things you have already seen.

Parameters

NameInTypeNotes
alert req path string The alert slug.

Body

FieldTypeNotes
email string?
webhook string? Slug of one of your team's webhooks.

Responses

201 The new destination.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/alerts/{alert}/destinations/{destination}

Stop delivering to one destination

Parameters

NameInTypeNotes
alert req path string The alert slug.
destination req path integer The destination id, from the list endpoint.

Responses

204 Removed.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

get /api/v1/alert-hits

What your alerts have found

A resumable stream, not an ordinary page. next_cursor is a resume token: store it, pass it back as cursor, and you get only what has arrived since. It keeps coming back non-null even when there is nothing new, so poll it rather than waiting for null.

Each hit carries the post, the alert, and which of its searches matched.

Parameters

NameInTypeNotes
alert query string Restrict to one alert, by slug.
limit query integer Rows per page, 1–100. Defaults to 50, or 20 for people and publications.
cursor query string The next_cursor from the previous response. Opaque — do not construct one. Omit for the first page.

Responses

200 A page of hits, oldest first.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

Webhooks

Reusable delivery endpoints owned by your team.

get /api/v1/webhooks

Your team's delivery endpoints

Registered once for the team and reusable by any of its alerts, in any project. The signing secret comes back on every read.

Responses

200 The webhooks.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

post /api/v1/webhooks

Register an endpoint

Registering one delivers nothing on its own — point an alert at it with POST /alerts/{alert}/destinations.

Body

FieldTypeNotes
name req string
url req string For Slack, its incoming webhook URL.
channel webhook | slack webhook sends signed JSON; slack sends Block Kit.

Responses

201 The new webhook, with its signing secret.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

patch /api/v1/webhooks/{webhook}

Rename, repoint, or switch off

Switching one back on also clears its failure streak, so an endpoint you have just fixed gets a fresh run of attempts rather than deactivating itself on its very next failure.

Parameters

NameInTypeNotes
webhook req path string The webhook slug.

Body

FieldTypeNotes
name string
url string
is_active boolean false stops delivery to this endpoint.

Responses

200 The updated webhook.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.

delete /api/v1/webhooks/{webhook}

Delete an endpoint

Blocked with a 422 while any alert still delivers here: cascading would silently mute those alerts, and you would find out by not being told about something. PATCH is_active to false instead.

Parameters

NameInTypeNotes
webhook req path string The webhook slug.

Responses

204 Deleted.
422 The request was rejected. code says why: validation_failed for a bad parameter, invalid_cursor for a cursor we did not issue, scope_conflict for mutually exclusive scopes passed together.
404 No such slug. code is not_found. If you passed a numeric id, the message says so — this API addresses everything by slug.
403 The key is valid but its team may not touch this row. code is forbidden.
401 Missing, malformed or revoked API key. code is unauthenticated.
402 The team has no active plan. code is no_plan. Unlike a rate limit, retrying will not help.
429 Too many requests. code is rate_limited; wait the number of seconds in the Retry-After header.