API intro

Markdown

A 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-hits instead 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.