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.
curl -G 'https://www.socialcrawl.dev/v1/notifications' \
-H 'x-api-key: YOUR_API_KEY' \
--data-urlencode 'unread=true' \
--data-urlencode 'limit=20'{
"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.
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"]}'{
"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.refusedis 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.refusedis 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_lefton thediscover.pausedevent 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 of your key. An agent signed in with OAuth needs the socialcrawl:read scope to list and socialcrawl:spend to mark read. See Authentication.
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 and event webhooks 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. 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 |
| 405 | METHOD_NOT_ALLOWED | Use the method in the Allow header |
| 429 | RATE_LIMITED | Wait for Retry-After seconds. See Rate limits |
Every error is free. See Errors for the envelope.
