API reference
MarkdownEvery operation on /api/v1. Authentication, paging, errors and rate limits are on the API intro page.
This page is generated from openapi.json. We check that file against the live routes on every build, in both directions.
On this page
Posts
Collections
Projects
Alerts
- get Your team's alerts
- post Create an alert
- get One alert
- patch Update an alert
- delete Delete an alert
- get What an alert looks for
- post Add a saved search
- patch Edit a saved search
- delete Switch a saved search off
- get Where an alert delivers
- post Point an alert somewhere
- delete Stop delivering to one destination
- get What your alerts have found
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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| post req | path | string | The post slug. |
| format | query | turns | markdown | turns returns JSON objects, one per turn. markdown returns the same page as readable text, laid out the way the web page shows it. Both page by turn. Over MCP the default is markdown. |
| 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 the transcript, in the requested shape. |
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| collection req | path | string | The collection slug. |
Body
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| collection req | path | string | The collection slug. |
Body
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| alert req | path | string | The alert slug. |
Body
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| alert req | path | string | The alert slug. |
Body
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| webhook req | path | string | The webhook slug. |
Body
| Field | Type | Notes |
|---|---|---|
| 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
| Name | In | Type | Notes |
|---|---|---|---|
| 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. |