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