API intro
MarkdownA JSON API over HTTPS at https://sputnikintelligence.com/api/v1. Everything the web app and MCP can do is available here.
This page covers the things that apply to every endpoint. The API reference lists each one.
Authentication
Send your API key as a bearer token.
curl -H "Authorization: Bearer $SPUTNIK_TOKEN" \
"https://sputnikintelligence.com/api/v1/posts?limit=5"
Create a key in the app under Settings → API keys. It is shown once. We store only a hash, so if you lose it, create a new one and revoke the old.
A key acts for the team it was created in. It does not follow you between teams. It stops working if you leave that team. See Teams.
Revoking takes effect immediately. Send every request over HTTPS, and keep the key out of URLs, repositories and browsers.
MCP uses a different credential. It signs you in through the browser, and an API key does not work against it. If your code cannot do an interactive sign-in, use this API.
Conventions
Everything is addressed by slug
A slug looks like k3mq-8w1p-ttz4. It comes back on every search and list result. We do not expose database ids. If you pass a number where a slug belongs, you get a 404 that tells you so.
Lists
{ "items": [...], "total": 1204, "next_cursor": "50" }
Every list endpoint returns this shape, including short ones like an alert's destinations.
Single items
{ "data": { "slug": "k3mq-8w1p-ttz4", "title": "..." } }
Timestamps
ISO 8601 in UTC, for example 2026-08-19T06:00:00Z. A null timestamp means we do not know it. Some feeds do not date their items.
Booleans
true, false, 1 and 0 all work in query strings.
Paging
Follow next_cursor until it comes back null. That is the only stop condition. A page shorter than your limit does not mean you are finished.
cursor=""
while :; do
page=$(curl -sG -H "Authorization: Bearer $SPUTNIK_TOKEN" \
--data-urlencode "q=your company" \
--data-urlencode "limit=100" \
--data-urlencode "cursor=$cursor" \
"https://sputnikintelligence.com/api/v1/posts/search")
echo "$page" | jq -c '.items[]'
cursor=$(echo "$page" | jq -r '.next_cursor // empty')
[ -z "$cursor" ] && break
done
The cursor is opaque. Pass back what we gave you. Do not build one or read anything out of it. An unrecognised cursor gives you a 422.
limit runs from 1 to 100. It defaults to 50, or 20 for people and publications. Asking for more than 100 gives you a 422 rather than a silent clamp.
total is null when we do not know the number cheaply. Null means unknown, not zero.
Two endpoints are capped instead of paged
/topics and /sponsors return at most 100 rows. next_cursor is always null and passing cursor gives you a 422. Narrow with q.
Alert hits are a resumable stream
/alert-hits uses the same shape for something different. Its next_cursor is a resume token. Store it, pass it back next time, and you get only what has arrived since. It stays non-null even when nothing is new, so poll it on a schedule.
Errors
{
"message": "Invalid cursor \"page-2\"; pass the next_cursor value from a previous response.",
"code": "invalid_cursor",
"errors": { "cursor": ["Invalid cursor \"page-2\"; ..."] }
}
Branch on code. The message is for a human to read and may be reworded. errors appears on validation failures and gives you per-field messages.
| Code | Status | Meaning |
|---|---|---|
validation_failed |
422 | A parameter was wrong. errors says which. |
invalid_cursor |
422 | A cursor we did not issue. Start again without one. |
scope_conflict |
422 | You passed two scopes that cannot be combined. |
not_found |
404 | No such slug. |
unauthenticated |
401 | Missing, malformed or revoked key. |
forbidden |
403 | Valid key, but its team cannot touch this. |
no_plan |
402 | The team has no active plan. |
rate_limited |
429 | Too many requests. |
Retry rate_limited after the seconds given in the Retry-After header. Do not retry anything else. A 402 in particular will not clear on retry: the team has no plan, and a client that treats it as a throttle will loop forever.
Rate limits
| Plan | Calls per month | Calls per day | Burst, per minute |
|---|---|---|---|
| Startup | 10,000 | 1,000 | 60 |
| Scale | 50,000 | 5,000 | 120 |
| Enterprise | custom | 50,000 and up | 300 |
The allowance is shared across the API, MCP and the CLI. One MCP tool call costs the same as one HTTP request. The monthly allowance resets on the 1st. The daily limit resets 24 hours after your first call of the day.
The per-minute limit stops one runaway process burning a day's allowance in a minute. It sits well above normal use.
The 429 body tells you which limit you hit and how long to wait.
{
"message": "You have hit the burst limit of 60 calls per minute...",
"code": "rate_limited",
"limit_type": "requests_per_minute",
"limit": 60,
"retry_after_seconds": 12
}
To use fewer calls:
- Raise
limit. A page of 100 costs the same as a page of 10. - Use search results to decide what is worth fetching in full.
- Poll
/alert-hitsinstead of re-running searches. - Scope searches to a collection.
A request that reached us counts, including one that failed validation. Our own 429s and 402s do not count. Month-to-date usage is on the billing page.
Generating a client
openapi.json is an OpenAPI 3.1 description of the whole API. It is public and needs no key. We check it against the live routes on every build.