SocialHz Engineering · 1 November 2026 · Requested by a customer
Background search: async=true, progress and a signed webhook
GET /api/v1/search?async=true · GET /api/v1/search/{id} · GET /api/v1/search/{id}/posts · DELETE /api/v1/search/{id}
What shipped
Add async=true to a search and it answers straight away with 202 Accepted and a search_id. We fetch every page on our side with no time limit, and you read the posts when it's done.
Why
A customer's weekly report covered many keywords over a date range. A single request runs for about two minutes, which wasn't enough for a search that size. And on Instagram, the platform's own date filter was leaving most posts in the range out.
1. Start
plan.max_credits is held up front, so you know the most the search can cost before it runs. Pages a keyword didn't need are given back as it finishes. pages is per keyword, 1 to 10, and 3 if you don't send it.
GET /api/v1/search?platform=instagram&query=Brightleaf or Northwind
&start_date=2026-10-01&end_date=2026-10-31&pages=5
&async=true&webhook_url=https://example.com/hooks/socialhz
HTTP/1.1 202 Accepted
Location: /api/v1/search/srch_7f3a9c21d4e8b6a0
{ "search_id": "srch_7f3a9c21d4e8b6a0", "status": "queued",
"plan": { "terms": ["Brightleaf", "Northwind"], "pages_per_term": 5,
"max_pages": 10, "credits_per_page": 1, "max_credits": 10 },
"credits": { "held": 10, "given_back": 0 },
"webhook_secret": "whsec_…" }2. Follow
- status: queued, running, done, failed (every credit given back) or cancelled.
- A keyword's ended: no_more_results, page_limit (it reached pages, and there may be more), failed or cancelled.
- percent is pages done over pages planned. The plan shrinks when a keyword ends early.
- When it's done: totals (total_posts, outside_dates_left_out, duplicates_left_out) and credits.charged, exactly what it cost.
GET /api/v1/search/srch_7f3a9c21d4e8b6a0
{ "status": "running",
"progress": { "pages_done": 4, "pages_planned": 10, "percent": 40, "posts_found": 37 },
"terms": [
{ "term": "Brightleaf", "status": "running", "pages_done": 3, "posts": 30 },
{ "term": "Northwind", "status": "done", "pages_done": 1, "posts": 7, "ended": "no_more_results" } ] }3. The webhook
When it finishes we POST search.finished to webhook_url. To check it came from us, v1 is the hex HMAC-SHA256 of <t>.<body>, keyed with the webhook_secret from step 1. Answer with any 2xx. Otherwise we try again after 30 seconds, then 2, 10 and 30 minutes. The address must be https and public, and redirects aren't followed.
X-SocialHz-Event: search.finished
X-SocialHz-Signature: t=1793522834,v1=5f2c…4. Read
GET /api/v1/search/{id}/posts?page=1&page_size=1000 (100 if not sent; term=Brightleaf for one keyword). total_posts and total_pages are exact, the order is fixed, and every post appears once with matched_terms. Read pages in any order, as often as you like: reading is free. Before the search is done, this answers 409 search_running.
Dates on Instagram
In a background search, Instagram dates are applied to each page's posts by publish time, rather than passed to Instagram's own date filter. In our tests that filter was leaving most of the posts in a range out. totals.outside_dates_left_out counts the posts dropped for falling outside your dates.
Cancel and retention
DELETE /api/v1/search/{id} stops a search. Pages already fetched are charged and kept to read, and the rest of the hold is given back. Results are readable for 14 days, then archived but still readable from the same URL with archived: true, until 180 days.
Switched on per workspace. Ask us and we'll turn it on for yours.
Request a feature
Tell us what you need on the updates page. The engineering team reads every request.