# Notifications (/docs/notifications) Read what needs your attention, such as a paused monitor, a failed Discover run or a refused call, with the next step to take and mark it read for free `GET /v1/notifications` lists what needs your attention on your account, newest first: a monitor whose webhook was paused, a Discover run that failed, a call refused for credits. It is the same list as the bell in the dashboard. Every item says what happened and carries a `next_step`, one concrete call to make or page to open, so an agent can act on it without knowing the kinds in advance. Both calls cost 0 credits. ```bash title="cURL" curl -G 'https://www.socialcrawl.dev/v1/notifications' \ -H 'x-api-key: YOUR_API_KEY' \ --data-urlencode 'unread=true' \ --data-urlencode 'limit=20' ``` ```json title="Response" { "success": true, "platform": "meta", "endpoint": "/v1/notifications", "data": { "items": [ { "id": "ntf_k3m7q2xwcbvd4nph6trz5ycgda", "kind": "job.paused", "severity": "critical", "title": "A monitor's webhook was paused", "message": "The webhook of the monitor \"Brand mentions\" was paused by the system. Reason: Webhook delivery failed 10 times in a row, so the webhook was paused. The monitor keeps running, but its results are no longer delivered.", "next_step": "The monitor keeps running and storing results: read them with GET /v1/monitors/mon_x1/runs, or use GET /v1/monitors/mon_x1/export on a monitor with numeric data. No API call re-enables the webhook yet. Fix your endpoint and save anything you need, because deleting the monitor removes its run history, then call DELETE /v1/monitors/mon_x1 and POST /v1/monitors to create it again. The new monitor has a new id, an active webhook and a new signing secret unless you supply one.", "params": { "jobKind": "monitor_webhook", "jobId": "mon_x1", "name": "Brand mentions", "reason": "webhook_auto_paused" }, "subject": { "kind": "monitor", "id": "mon_x1" }, "url": "https://www.socialcrawl.dev/docs/webhooks", "occurrences": 1, "first_occurred_at": "2026-11-01T09:12:40.118Z", "last_occurred_at": "2026-11-01T09:12:40.118Z", "read_at": null } ], "next_cursor": null, "unread_count": 1 }, "credits_used": 0, "credits_remaining": 4999, "request_id": "req-XXXXX", "cached": false, "pagination": { "next_cursor": null, "has_more": false } } ``` `unread_count` counts the whole inbox, not only this page. Only your account's items are ever returned. An empty inbox answers `200` with an empty `items` array and a `data.empty_reason_note` that says whether the inbox is empty or your filter matched nothing. ## How do I mark items read? Send the ids you handled, or `{"all": true}` to mark everything read. Send exactly one of the two. Ids that belong to another account are ignored, and `updated` counts only the items that were unread, so repeating a call returns 0. ```bash title="cURL" curl -X POST 'https://www.socialcrawl.dev/v1/notifications/read' \ -H 'x-api-key: YOUR_API_KEY' \ -H 'Content-Type: application/json' \ -d '{"ids": ["ntf_k3m7q2xwcbvd4nph6trz5ycgda"]}' ``` ```json title="Response" { "success": true, "platform": "meta", "endpoint": "/v1/notifications/read", "data": { "updated": 1, "unread_count": 0 }, "credits_used": 0, "credits_remaining": 4999, "request_id": "req-XXXXX", "cached": false } ``` ## What kinds of notification are there? Each item has a `kind`, and its `severity` (`info`, `warning` or `critical`) is fixed by the kind. | Kind | Severity | Raised when | | ---- | -------- | ----------- | | `job.failed` | `warning` | A Monitor run fails (a run that found nothing and was refunded counts, and so does a run skipped because the balance could not cover it), a scheduled Discover run fails, or a Discover run you started fails | | `job.paused` | `critical` | The system pauses something you have to act on: a Monitor's webhook, a Discover schedule or run, or a listening stream | | `dataset.ready` | `info` | A Discover run completes, or ends partial, with results, or the dashboard's Build assistant saves a dataset with at least one row | | `credits.refused` | `critical` | A `/v1` call, or a step inside one, was refused with a `402` because your balance was too low | | `credits.low` | `warning` | Your balance crossed your low-credit alert threshold, when your low-credit alert is on | | `defect.fixed` | `info` | We fixed something you reported to us | | `release.published` | `info` | A new release is out. It is added the first time you read your notifications after the release, and an account created after the release date gets none | `params` carries the values the text was built from, and its keys depend on the `kind`. For a `job.*` item, `jobKind` is `monitor`, `monitor_webhook`, `discover_schedule`, `discover_run` or `stream`, `jobId` is the id to pass to that job's own routes, and `reason` is a machine code when one exists. `subject` names the object the item is about. ## How are repeats folded? A repeat of the same fact is one item, not a new row: `occurrences` goes up, `last_occurred_at` moves forward, and `read_at` goes back to `null` so the item surfaces again. For `job.failed` and `job.paused` a fact is one job on one UTC day, so a monitor that fails every hour shows one item per day with a rising `occurrences`. Two kinds are written once and then stay as you left them. `credits.refused` is one item per UTC day, so it does not come back unread within the day after you marked it read, and `release.published` is one item per release. Items that nothing has touched for 90 days are deleted. ## What is not recorded? - **A call stopped by the key's own limit.** `credits.refused` is raised only when the account balance is the reason. A call stopped by the key's own credit limit is not recorded, whatever error type the route answered with, and buying credits would not lift that limit. - **A refusal storm.** At most one `credits.refused` is written per account every 10 minutes. The refused calls themselves are in your request log on the dashboard. - **A pause we undo ourselves.** A Discover run paused because our service was briefly unavailable, or because it waited too long for a free slot, is resumed automatically. You are told only when the automatic resumes are used up, which is when `data.auto_resumes_left` on the `discover.paused` event reaches 0. After that only you can resume it. - **A pause you asked for.** Pausing a listening stream yourself raises nothing. ## What happens when a Monitor's webhook is paused? The system only ever pauses a Monitor's webhook, never the Monitor. The Monitor keeps running, storing results and billing as usual, and `GET /v1/monitors/{monitor_id}` shows `webhook.status` as `paused`. No API call turns the webhook back on. To get webhooks again, fix your endpoint, export what you need, then delete the Monitor and create it again. The new Monitor has a new id and a new signing secret. The `next_step` on the item says the same, with your Monitor's id filled in. ## How should an agent poll it? Poll `GET /v1/notifications?unread=true` no more often than once a minute, which is how often the dashboard checks. For each item, act on `next_step`, then send its `id` to `POST /v1/notifications/read` so it does not come back. Page with `pagination.next_cursor`: send it back as `cursor`, unchanged. The `data.next_cursor` on the same response is the same position, and either is accepted. A cursor this route did not issue is a `400`. Filter with `kind`, comma-separated, for example `kind=job.failed,job.paused`; an unknown kind is a `400` that lists the allowed values. Use `GET` (or `HEAD`) on the list and `POST` on `/read`. Any other method answers `405` with an `Allow` header. Both calls count toward the normal [rate limit](/docs/rate-limits.md) of your key. An agent signed in with OAuth needs the `socialcrawl:read` scope to list and `socialcrawl:spend` to mark read. See [Authentication](/docs/authentication.md). ## How does it relate to events and webhooks? Notifications are a per-account inbox of things that need a human or an agent to look: they are written in plain text and fold repeats. The [events feed](/docs/events.md) and [event webhooks](/docs/webhook-endpoints.md) are the ordered log of what happened to your Discover runs and listeners, with ids and counts, for a program to process every time. A Monitor's own results arrive on its [webhook](/docs/webhooks.md). Notifications have no webhook: if you need to be pushed something, subscribe to the events, and use notifications to catch what a push cannot, such as a webhook that stopped receiving. ## Errors | Status | `error.type` | What to do | | ------ | ------------ | ---------- | | 400 | `INVALID_REQUEST` | Fix the field named in `error.details.param`. `error.details.next_step` names the value to send | | 401 | `MISSING_API_KEY` or `INVALID_API_KEY` | Send a valid `x-api-key`. See [Authentication](/docs/authentication.md) | | 405 | `METHOD_NOT_ALLOWED` | Use the method in the `Allow` header | | 429 | `RATE_LIMITED` | Wait for `Retry-After` seconds. See [Rate limits](/docs/rate-limits.md) | Every error is free. See [Errors](/docs/errors.md) for the envelope. - [Events feed](/docs/events.md): Read the ordered log of Discover and listening events, or wait for new ones. - [Monitor webhooks](/docs/webhooks.md): Signed deliveries for each Monitor run, and what the auto-pause means. - [For AI agents](/docs/ai-agents.md): The free calls an agent should make before it writes code.