# SnitchFeed API: endpoints, base URL, errors (https://snitchfeed.com/docs/api/introduction)





The SnitchFeed REST API does two things:

* **Reads your organization's data** (your workspace): mentions, listeners, feeds, Brand Context and credit usage, and lets you mark mentions seen or engaged.
* **Searches public posts** on LinkedIn, X (Twitter) and Hacker News, and fetches LinkedIn company pages, person posts and comments, and post comment or reaction scrapes. These calls spend credits.

## Endpoints [#endpoints]

### Your organization's data [#your-organizations-data]

| Method  | Path                       | Description                                                                                    |
| ------- | -------------------------- | ---------------------------------------------------------------------------------------------- |
| `GET`   | `/v1/mentions`             | List mentions your listeners matched, cursor-paginated, sorted by newest or by Fit Score       |
| `GET`   | `/v1/mentions/{id}`        | Get one mention                                                                                |
| `GET`   | `/v1/mentions/count`       | Count mentions matching a set of filters                                                       |
| `PATCH` | `/v1/mentions/{id}/state`  | Mark a mention seen or engaged. See [mention state](#mention-state)                            |
| `GET`   | `/v1/listeners`            | List listeners                                                                                 |
| `GET`   | `/v1/listeners/{id}`       | Get one listener                                                                               |
| `GET`   | `/v1/feeds`                | List saved feeds                                                                               |
| `GET`   | `/v1/feeds/{id}`           | Get one feed                                                                                   |
| `GET`   | `/v1/organization/context` | Brand Context for the organization (the same information as the Brand Context page in the app) |
| `GET`   | `/v1/organization/usage`   | Credit usage for the current billing period                                                    |

These endpoints do not consume credits.

### Public social search [#public-social-search]

| Method | Path                                | Description                                                   |
| ------ | ----------------------------------- | ------------------------------------------------------------- |
| `POST` | `/v1/data/twitter/tweets/search`    | Search posts on X                                             |
| `POST` | `/v1/data/linkedin/posts/search`    | Search posts on LinkedIn                                      |
| `POST` | `/v1/data/linkedin/company/get`     | Fetch a LinkedIn company page                                 |
| `POST` | `/v1/data/linkedin/person/posts`    | Fetch posts from a LinkedIn person profile                    |
| `POST` | `/v1/data/linkedin/person/comments` | Fetch comments written **by** a LinkedIn person (profile URL) |
| `POST` | `/v1/data/linkedin/posts/comments`  | Scrape comments **on** a LinkedIn post (post URL)             |
| `POST` | `/v1/data/linkedin/posts/reactions` | Scrape reactions on a LinkedIn post                           |
| `GET`  | `/v1/data/linkedin/industries`      | Search LinkedIn industry codes (free)                         |
| `POST` | `/v1/data/hackernews/search`        | Search Hacker News stories and comments                       |

Every `POST /v1/data/*` call costs credits, charged only when it succeeds. Costs per endpoint and what happens when you run out are on [API credits](/docs/api/credits). LinkedIn search, person posts and person comments always return the full page.

Mentions, listeners and feeds use prefixed public IDs (`men_...`, `lis_...`, `svw_...`) rather than sequential numbers.

The endpoint reference pages in this section list every parameter and response field. The machine-readable spec is at `GET https://api.snitchfeed.com/v1/openapi.json`.

## Base URL [#base-url]

```
https://api.snitchfeed.com/v1
```

Paths above include `/v1`. For example: `POST https://api.snitchfeed.com/v1/data/twitter/tweets/search`.

## Authentication [#authentication]

Send `Authorization: Bearer <token>` on every request. The token is either an API key (`sf_api_...`) for your own organization or an OAuth access token for an integration other customers install. See [Authentication](/docs/api/authentication) and [OAuth](/docs/api/oauth).

<Callout type="warn">
  The credit-metered `POST /v1/data/*` endpoints currently accept API keys only. An OAuth access token on those endpoints returns `401`. OAuth tokens work on every organization data endpoint and on `GET /v1/data/linkedin/industries`.
</Callout>

## Mention state [#mention-state]

Every mention carries a `state` object: `seen`, `lastSeenAt`, `firstSeenAt`, `engaged`, `engagedAt` and `archived`. State is shared across the whole organization, like read and unread in an inbox. Filter `GET /v1/mentions` and `GET /v1/mentions/count` with `seen=true|false` and `engaged=true|false`.

`PATCH /v1/mentions/{id}/state` takes `seen` and/or `engaged` (booleans; at least one is required) and an optional `occurredAt`. Fields you leave out are unchanged. Setting `engaged: true` also marks the mention seen; `engaged: false` does not change whether it is seen.

```json
{ "seen": true, "engaged": true, "occurredAt": "2026-01-15T10:30:00Z" }
```

This endpoint needs both the `mentions:read` and `mentions:write` scopes and does not consume credits. `occurredAt` is an ISO 8601 time for when the change happened. It defaults to now and must not be in the future.

The current value of each dimension (seen, engaged) comes from the event with the latest `occurredAt`. An event older than the current latest one is recorded but does not change the state. The response returns the mention with its resulting `state`.

Each change is attributed to your API key or OAuth app, acting for the user who owns it, and shows up that way in the app. Archiving and unarchiving are not available through the API. State changes are not pushed to you, so fetch a mention again to see changes made in the app.

## Pagination [#pagination]

Each endpoint family paginates differently:

| Endpoint                                                    | Request                                                                   | Response                                                     | Notes                                                                                                                                                              |
| ----------------------------------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GET /v1/mentions`                                          | `cursor`, `limit` (1 to 100, default 25), `sort` (`newest` or `fitScore`) | `meta.hasMore`, `meta.nextCursor`                            | A cursor only works with the sort it was created for. `newest` is gap-free; `fitScore` can occasionally skip or repeat a mention whose score changed between pages |
| `GET /v1/listeners`, `GET /v1/feeds`                        | None                                                                      | Full list                                                    | Not paginated                                                                                                                                                      |
| `POST /v1/data/twitter/tweets/search`                       | `cursor`, `count` (up to 20)                                              | `meta.hasMore`, `meta.nextCursor`                            |                                                                                                                                                                    |
| `POST /v1/data/linkedin/posts/search`                       | `page` (starts at 1)                                                      | `meta.page`, `meta.returnedCount`                            | No `hasMore`; request the next page until it comes back empty                                                                                                      |
| `POST /v1/data/linkedin/person/posts`, `/person/comments`   | `page` (starts at 1), `paginationToken`                                   | `meta.hasMore`, `meta.paginationToken`                       | Send the returned token with the next page number                                                                                                                  |
| `POST /v1/data/linkedin/posts/comments`, `/posts/reactions` | `maxComments` or `maxReactions`                                           | One response                                                 | Not paginated; set how many items to scrape                                                                                                                        |
| `GET /v1/data/linkedin/industries`                          | `q`, `page` (starts at 1)                                                 | `meta.total`, `meta.page`, `meta.pageCount`, `meta.pageSize` |                                                                                                                                                                    |
| `POST /v1/data/hackernews/search`                           | `page` (starts at 0), `hitsPerPage` (up to 100)                           | `meta.hasMore`, `meta.nextPage`                              |                                                                                                                                                                    |

## Response format [#response-format]

All responses are JSON and include a `requestId`. Successful responses use `2xx` status codes. Errors use this envelope:

```json
{
  "requestId": "01HXYZABCDEF",
  "error": {
    "code": "unauthorized",
    "message": "Invalid or revoked API key."
  }
}
```

Validation errors add an `issues` array of `{ path, message }`. Rate limit errors add `error.retryAfter` (seconds) and a matching `Retry-After` header.

## Error codes [#error-codes]

| HTTP  | `code`                 | When                                                                                                                                                                                       |
| ----- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `400` | `validation_error`     | The body or query string is invalid. `issues` lists each field. LinkedIn post comment and reaction scrapes also return this when you don't have enough credits, with an issue on `credits` |
| `401` | `unauthorized`         | Missing or malformed `Authorization` header, or an invalid, revoked or expired API key or OAuth token                                                                                      |
| `403` | `forbidden`            | The credential lacks the scope for this endpoint, the user behind it no longer has access, or (API keys only) the organization's plan does not include API access                          |
| `404` | `not_found`            | The mention, listener, feed or organization does not exist or belongs to another organization                                                                                              |
| `429` | `rate_limited`         | The per-minute limit or the in-flight cap was hit. See [rate limits](/docs/api/rate-limits)                                                                                                |
| `502` | `upstream_error`       | The upstream search failed. Also returned when X, Hacker News or LinkedIn search, company and person calls don't have enough credits                                                       |
| `502` | `billing_unavailable`  | `GET /v1/organization/usage` could not load billing data                                                                                                                                   |
| `503` | `linkedin_unavailable` | LinkedIn search is temporarily unavailable. You are not charged; retry later                                                                                                               |

Failed requests are not charged credits. Out-of-credit responses differ by endpoint; [API credits](/docs/api/credits#when-you-run-out-of-credits) has the full list.

## Rate limits [#rate-limits]

Limits apply per organization: **20 requests per minute** per search surface (X, LinkedIn, Hacker News), **60 per minute** shared by the organization data endpoints, and **at most 2 in-flight requests** across all v1 endpoints. See [Rate limits](/docs/api/rate-limits).

## Request history [#request-history]

Open **Integrations → API** in the app ([app.snitchfeed.com/integrations/api](https://app.snitchfeed.com/integrations/api)) and select the **Usage** tab to see recent API requests from your organization: time, key, user, route, status, HTTP code and duration. Expand a row for details. The tab needs a plan that includes API access.

## Related [#related]

* [API authentication](/docs/api/authentication): create an API key and send it
* [API scopes and permissions](/docs/api/scopes): which scope each endpoint needs
* [API credits](/docs/api/credits): what each search endpoint costs
* [Webhook events, retries and delivery limits](/docs/api/webhooks): get new mentions pushed to you
* [SnitchFeed MCP server](/docs/mcp): use SnitchFeed from an AI client instead
