# Social Listening API: 4 Calls, Once Each (https://www.socialcrawl.dev/blog/social-listening-api) > 25.92 GBP is the 3-page upper figure for Reddit and YouTube every 1h. On 8 Nov 2026 the check spent 6 credits and stored a baseline, not a later mention. Social listening is a listener you create once for a keyword or boolean topic, priced first with `?dry_run=1`. Once each is the rule for a mention after that baseline. For a tools list, read [best social listening tools](/blog/best-social-listening-tools). You can stop writing the loop that polls the same keyword search and then drops, by hand, the posts you already stored. On 8 Nov 2026 the four calls below priced a listener, created it, read its mentions, and registered a webhook. The topic was the coffee method pour over, not a brand. The blocks below are shortened fields from the capture. That run stored a baseline and did not show a later mention. The listener was deleted before the next fire. The dry run cost 0 credits. The quote for Reddit and YouTube was 25.92 GBP as the 3-page upper figure. ## What does a social listening API quote before you create it? On 8 Nov 2026 a social listening API dry run quoted 1,440 credits a month for Reddit and YouTube, 25.92 GBP as the 3-page upper figure, and 0 credits to ask. The call was `POST /v1/listening/streams?dry_run=1` at 02:08:07Z. It returned HTTP 200 and `credits_used` 0. The body was the one you would create: keywords `["pour over"]`, platforms Reddit and YouTube, `schedule.every` `1h`, and `schedule.name` `pour over chatter`. ```bash curl -X POST "https://www.socialcrawl.dev/v1/listening/streams?dry_run=1" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"keywords":["pour over"],"platforms":["reddit","youtube"],"schedule":{"every":"1h","name":"pour over chatter"}}' ``` The read-back was Posts mentioning “pour over”. The lanes on that quote were `reddit/search` and `youtube/search/advanced`. Both were `newest_first`, `max_pages` 3, and `empty_pages_refunded` true. That refund flag is the lane's rule, and this listener did not receive that refund. The quote returned `credits_per_hour` 2, `credits_per_month` 1,440, `depth_range_credits_per_month` 1,440 to 4,320, `recommended_max_credits_per_month` 4,911, and `money_per_credit` 0.006 GBP on basis `starter_list`. Each platform was 720 credits a month. `money_per_month` was 25.92 GBP. That 25.92 GBP is the 3-page upper figure on the response, not a promise of a typical month. `screen.result` was `allowed`. See [priced before you create it](/docs/listening/pricing) for how that price is calculated. ```json { "credits_used": 0, "data": { "read_back": "Posts mentioning “pour over”", "lanes": [ { "lane": "reddit/search", "ordering": "newest_first", "max_pages": 3, "empty_pages_refunded": true }, { "lane": "youtube/search/advanced", "ordering": "newest_first", "max_pages": 3, "empty_pages_refunded": true } ], "estimate": { "credits_per_hour": 2, "credits_per_month": 1440, "depth_range_credits_per_month": [1440, 4320], "recommended_max_credits_per_month": 4911, "money_per_credit": { "amount": 0.006, "currency": "GBP", "basis": "starter_list" }, "money_per_month": { "amount": 25.92, "currency": "GBP" } }, "screen": { "result": "allowed" } } } ``` The block is shortened fields from the capture. The create, at 02:09:12Z, was the same body plus `schedule.max_credits_per_month` 4911, the recommended cap on this response. A different body can return a different cap. ```bash curl -X POST "https://www.socialcrawl.dev/v1/listening/streams" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"keywords":["pour over"],"platforms":["reddit","youtube"],"schedule":{"every":"1h","name":"pour over chatter","max_credits_per_month":4911}}' ``` The response was HTTP 201, `credits_used` 0, id `lst_m5DZU2OVQsBzbhd9p1C2U`. Baseline was `establishing`. Spend was gross 0, refunded 0, net 0. Lane `last_check_at` was null. Caps were 4,911 for the month and 6 for a run. `last_fire_at` was 2026-11-08T02:09:11.761Z. `next_fire_at` was 2026-11-08T03:09:11.713Z, the next hour on 8 Nov 2026. The check finished before the mentions read. After that check, both the delete and the confirm GET showed month `2026-11` spend gross 6, refunded 0, net 6, baseline `established`, and `last_check_at` 2026-11-08T02:09:18.551Z. That 6 is one slot at the run cap of 6. The runs route was not called, so there is no per-page line. `credits_refunded` stayed 0. There is no `no_new_items` line, and there was no second check. A small blank card with a single notch beside a rounded bell, one stored mention from keyword monitoring on social media. ## How do you run keyword monitoring on social media? You read the mentions the listener kept. On 8 Nov 2026 that was `GET /v1/listening/streams/lst_m5DZU2OVQsBzbhd9p1C2U/mentions?limit=5` at 02:09:45Z, HTTP 200, `credits_used` 0. ```bash curl "https://www.socialcrawl.dev/v1/listening/streams/lst_m5DZU2OVQsBzbhd9p1C2U/mentions?limit=5" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` The page had five rows, `has_more` true, and `dropped` 0, so this page is not the whole set. All five had baseline true and platform `youtube`. Reddit was not on this page and was not counted. Only one row's text is quoted below. The row is mention id `men_00816399bfd354856ea6b14c`, platform `youtube`, baseline true, `record_type` `video`, `matched_keywords` `["pour over"]`, `literal_match` true, relevance `p` 0.85, and `first_seen_at` 2026-11-08T02:09:17.458Z. The text field was 233 characters. The JSON below keeps the first 80: `Downtown Seattle's new pour-over spot? Narrative Coffee #coffee #pourover #seat`. Those scores are returned fields, and this run has no accuracy rate. The JSON uses shortened field names from the capture. ```json { "mention_id": "men_00816399bfd354856ea6b14c", "first_seen_at": "2026-11-08T02:09:17.458Z", "platform": "youtube", "baseline": true, "record_type": "video", "literal_match": true, "text": "Downtown Seattle's new pour-over spot? Narrative Coffee #coffee #pourover #seat", "matched_keywords": ["pour over"], "relevance": "p 0.85" } ``` This page is the baseline. The rows are readable, and this run did not show a later mention. ## How is a social media listening API different from polling? After the mentions read above, this run also used a social media listening API for a named events consumer and a webhook registration. Standing polls against a monitoring route are a different post, [social media monitoring](/blog/social-media-monitoring-api). The events call was `GET /v1/events?consumer=blog-listening-demo&types=mention.created&start=now&limit=5` at 02:10:58Z. HTTP 200, `credits_used` 0. It was not acknowledged. ```bash curl "https://www.socialcrawl.dev/v1/events?consumer=blog-listening-demo&types=mention.created&start=now&limit=5" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` The call returned 0 items, `unread` 0, `has_more` false, `dropped` 0, and `empty_reason` `no_matches`. The response note was: "The source answered and has no rows for this request. The same call returns the same answer, so change the query or the filters to get rows." ```json { "credits_used": 0, "unread": 0, "has_more": false, "dropped": 0, "empty_reason": "no_matches" } ``` `start=now` ran at 02:10:58Z, about 100 seconds after the check (`last_check_at` 2026-11-08T02:09:18.551Z). This empty page does not prove the check wrote no `mention.created` events. There was no second events read. Consumer `blog-listening-demo` is bound to `mention.created` with `subject_ids` null. It listed 5 listeners on this key, including this one, with both lanes `health` ok and `checks_since_last_read` 0. The feed is [the events feed](/docs/events). `POST /v1/webhook-endpoints` targeted `https://example.com/hooks/socialcrawl-listening-demo`, type `mention.created`, `subject_ids` this listener. HTTP 201, `credits_used` 0, status `enabled`, id `we_kThLyRoMOlhGqIDS36a7c`, `created_at` 2026-11-08T02:12:36.771Z. `data.secret` was returned and was not stored. The webhook docs, read on this run, require HTTPS on port 443 and a public host. This URL was accepted. The URL was the example.com hook, the test send was not called, and attempts were not read. ```bash curl -X POST "https://www.socialcrawl.dev/v1/webhook-endpoints" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"url":"https://example.com/hooks/socialcrawl-listening-demo","types":["mention.created"],"subject_ids":["lst_m5DZU2OVQsBzbhd9p1C2U"]}' ``` ```json { "credits_used": 0, "data": { "endpoint": { "id": "we_kThLyRoMOlhGqIDS36a7c", "status": "enabled", "created_at": "2026-11-08T02:12:36.771Z" } } } ``` DELETE on that endpoint returned HTTP 200, `deleted` true, `credits_used` 0, in the same minute (02:12:36Z). No payload arrived. The listener was deleted before the next fire, so the 201 is the registration. The register steps are in [register the webhook](/docs/webhook-endpoints). A single pour-over coffee dripper next to a kettle, the gear named in the listener rule rather than a brand. ## What did the boolean dry run read back? The boolean call was a dry run at 02:08:08Z, HTTP 200, `credits_used` 0, and the listener was not created. The rule was `"pour over" AND (dripper OR kettle) NOT fruit`. The context was `pour-over coffee equipment, not fruit`. The name was `pour over gear`. The platform was Reddit. The schedule was every `1h`. ```bash curl -X POST "https://www.socialcrawl.dev/v1/listening/streams?dry_run=1" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" \ -H "Content-Type: application/json" \ -d '{"rule":"\"pour over\" AND (dripper OR kettle) NOT fruit","context":"pour-over coffee equipment, not fruit","platforms":["reddit"],"schedule":{"every":"1h","name":"pour over gear"}}' ``` The read-back was Posts mentioning “pour over”, together with “dripper” or “kettle”, but not “fruit”. `fetch_terms` was `"pour over"`. `local` was `(dripper OR kettle) AND NOT fruit`. The operators in this rule were AND, OR, NOT, double quotes, and brackets. The full grammar is the [listening reference](/docs/listening/reference). The estimate, still a dry run, was 720 credits a month, depth 720 to 2,160, slot max 3, recommended cap 2,456, and 12.96 GBP. The credit price was the same 0.006 GBP on `starter_list`. `credits_used` was 0. The screen was allowed. The lane was `reddit/search`, newest first, 3 pages, empty pages refunded. That flag is the lane's rule on a listener that was never created, so nothing was refunded. The response also had expected net 719. The listener that ran was the keyword body above, not this rule, and the 25.92 GBP 3-page upper figure stays on that keyword quote. ```json { "credits_used": 0, "read_back": "Posts mentioning “pour over”, together with “dripper” or “kettle”, but not “fruit”", "fetch_terms": "\"pour over\"", "local": "(dripper OR kettle) AND NOT fruit", "screen": { "result": "allowed" }, "estimate": { "credits_per_month": 720, "depth_range_credits_per_month": [720, 2160], "max_credits_per_slot": 3, "recommended_max_credits_per_month": 2456, "money_per_credit": { "amount": 0.006, "currency": "GBP", "basis": "starter_list" }, "money_per_month": { "amount": 12.96, "currency": "GBP" } }, "lane": { "lane": "reddit/search", "ordering": "newest_first", "max_pages": 3, "empty_pages_refunded": true } } ``` ## Which lanes refund an empty page, and which do not? This table is the lanes page as read on 8 Nov 2026, file modified 2 Nov 2026, and it is not the pour-over listener's spend. The dry run matched the docs on the two priced lanes only, `reddit/search` and `youtube/search/advanced`. The other rows were not called. The page is [which lanes refund an empty page](/docs/listening/lanes). | Lane | Order | Shortest `every` | Empty page refunded | Pages | | --- | --- | --- | --- | --- | | `reddit/search` | Newest first | `30m` | Yes | 3 | | `youtube/search/advanced` | Newest first | `30m` | Yes | 3 | | `reddit/search/comments` | Newest first | `30m` | Yes | 3 | | `twitter/search/tweets` | Newest first | `30m` | Yes | 3 | | `naver/news/search` | Newest first | `30m` | Yes | 3 | | `naver/blog/search` | Top results | `1h` | No | 3 | | `tiktok/search` | Top results | `1h` | No | 1 | | `facebook/search/posts` | Top results | `1h` | No | 2 | | `threads/search` | Top results | `1h` | No | 1 | | `linkedin/search/posts` | Top results | `6h` | No | 3 | | `google_news/search` | Time window | `1h` | No | 1 | Newest first, shortest `30m`, empty page refunded, 3 pages: `reddit/search`, `youtube/search/advanced`, `reddit/search/comments`, `twitter/search/tweets`, and `naver/news/search`. Naver news at 30 minutes is a docs row, not this listener. This listener was Reddit and YouTube every 1h. Top results, shortest `1h`, not refunded: `naver/blog/search` at 3 pages, `tiktok/search` at 1, `facebook/search/posts` at 2, and `threads/search` at 1. `linkedin/search/posts` is top results, `6h`, not refunded, 3 pages. `google_news/search` is a time window, `1h`, not refunded, 1 page. The lanes page says that time window is not a media-monitoring feed. Google News was not called on this run. `reddit/search/comments` is a row in the table. This run did not call it. Empty-page refunded is the lanes-file mark on those newest-first rows. This run has no `no_new_items` line. `credits_refunded` was 0, and there was no second check. This listener did not receive that refund. Account panels were not called. The reference, read on 8 Nov 2026, documents up to 300 accounts and `schedule.purpose`. The account-lane rows in the lanes file were also not called: newest first, `30m`, empty page refunded, 2 pages, and 1 page on Threads. The lanes file has no Instagram lane. ## What does this listener not do? This post creates the listener through the API and does not show a screen. The run priced one keyword body, stored a baseline on the first check, and was deleted at 02:12:37Z. The next fire was 03:09:11Z (response field 2026-11-08T03:09:11.713Z). There was no second check. `credits_refunded` was 0, and there is no `no_new_items` line. There is no backfill. History before the listener exists is Discover with `since`. The path is [posts from before the listener exists](/docs/prism/discover). This run did not call Discover. The mentions payload has no sentiment score, no share of voice, and no analytics rollup. The 0.85 on the sample row is a returned field. The lanes file has no Instagram lane. Listening is not real-time. The shortest interval on the docs table is 30 minutes, and this listener was every `1h`. The two priced lanes have `max_pages` 3. The runs route was not called, so there is no per-page line. `reddit/search/comments` was not called. Facebook comments and Telegram are not in the rows copied here, and this run did not call Facebook or Telegram. This post is not a guide to government, intelligence, or protest monitoring. It is not a hosted MCP wrapper. The listener was deleted before the next hour, so this one check is not a reliability measurement. People to contact are a different job. See [leads from listening](/blog/social-listening-leads-api). ## How do you start a listener and delete it before the next hour? Price the body you would create with `POST /v1/listening/streams?dry_run=1`, create it with the `recommended_max_credits_per_month` that your own dry run returns, read mentions, and delete the listener before the next hour if you do not want the next check. 1. Send the dry run with your keyword. This run's body is in the first section. 2. Create with the recommended cap from that response. This run sent 4,911 because that was the cap on this quote. 3. Read mentions on the id the create returned. This run used `GET /v1/listening/streams/lst_m5DZU2OVQsBzbhd9p1C2U/mentions?limit=5`. 4. Delete before the next hour if you do not want the next check. This run deleted at 02:12:37Z. The response was HTTP 200, `credits_used` 0, `status` `deleted`, `next_fire_at` null, baseline `established`, spend net 6. The confirm GET was HTTP 200 with that tombstone, not 404. ```bash curl -X DELETE "https://www.socialcrawl.dev/v1/listening/streams/lst_m5DZU2OVQsBzbhd9p1C2U" \ -H "x-api-key: $SOCIALCRAWL_API_KEY" ``` You send one API key in the `x-api-key` header. The [listening overview](/docs/listening/overview) says what a listener is. The [listening quickstart](/docs/listening/quickstart) has the same price, create, and read steps. ## Frequently asked questions ### What is social listening? Social listening is a listener you create once for a keyword or a boolean topic. You price that body before you create it. Once each is the rule for a mention after that baseline. On 8 Nov 2026 the first check stored a baseline. This run did not show a later delivery. ### How much did this listener cost before it was created? The dry run cost 0 credits to ask. It quoted 2 credits an hour, `credits_per_month` 1,440, a depth range of 1,440 to 4,320, each platform 720 credits a month, a recommended cap of 4,911, and 25.92 GBP as the 3-page upper figure at 0.006 GBP per credit on `starter_list`, for Reddit and YouTube every 1h. The create response had `credits_used` 0. The finished first check, month 2026-11, showed spend gross 6, refunded 0, net 6. ### Does the first check send old posts? Create came back with baseline `establishing`, and after the check the listener was baseline `established`. All five mentions on the page had baseline true and were readable on the mentions route. This run did not show a later delivery. The events read does not prove the check wrote no `mention.created` events. ### Does a social listening API include sentiment, a dashboard, or real-time results? The mentions payload has no sentiment score, no share of voice, and no analytics rollup. This post creates the listener through the API and does not show a screen. It is not real-time. The shortest interval on the docs table is 30 minutes, and this listener was every 1h. The lanes file has no Instagram lane. History before the listener exists is [Discover](/docs/prism/discover) with `since`. The two priced lanes have `max_pages` 3. This run's `credits_refunded` was 0. This is not a guide to government, intelligence, or protest monitoring. ### Why did the events read return zero rows? The call used consumer `blog-listening-demo`, types `mention.created`, and `start=now`, at 02:10:58Z, and it returned 0 rows with `empty_reason` `no_matches`. That was about 100 seconds after the check, so it does not prove the check wrote no `mention.created` events. The webhook returned HTTP 201 and was deleted in the same minute. The secret was not stored. The test send was not called. Attempts were not read. No payload arrived.