Batch requests
Queue up to 100 requests per call with POST /v1/batches at half the credits. Poll GET /v1/batches/{id}, wait with the SDKs, or receive a signed webhook.
When you don’t need the answer while you wait (rank tracking, nightly enrichment, dataset building), send the work to the batch lane. POST /v1/batches queues up to 100 requests for one endpoint and answers right away. Each request becomes its own job, costs half the realtime price, and is delivered by polling or by webhook.
Realtime (POST /v1/search, …) |
Batch (POST /v1/batches) |
|
|---|---|---|
| Requests per call | 1 | 1 to 100, all for one endpoint |
| Response | 200 with the result |
202 with one job per request |
| Latency | Seconds | Target: done within 15 minutes |
| Price | Normal price | 0.5× the realtime price |
| Delivery | In the response | Poll GET /v1/batches/{id} or webhook |
Realtime endpoints take one JSON object per call. A JSON array body is rejected with 400 invalid_request; to run many queries, either send them in parallel within your rate limit or queue them here.
Create a batch
Send the endpoint name, the request bodies, and optionally a webhook_url:
curl https://api.serpkite.com/v1/batches \
-H "Authorization: Bearer $SERPKITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"endpoint":"search","requests":[{"q":"best espresso machine","country":"us"},{"q":"best espresso machine","country":"de","language":"de"}],"webhook_url":"https://example.com/hooks/serpkite"}'
# then poll a job (free)
curl https://api.serpkite.com/v1/batches/0192f7a4-6c1e-7b3a-9d2f-5e8a1c4b7d90 \
-H "Authorization: Bearer $SERPKITE_API_KEY"| Field | Type | Description |
|---|---|---|
endpoint required |
string | One of search, images, videos, news, maps, places, reviews, shopping, scholar, patents, autocomplete, lens, webpage. |
requests required |
array | 1 to 100 request objects. Each takes the same fields as the realtime endpoint, including num: 100, format, fields, include_content and max_age. |
webhook_url |
string | HTTPS URL that receives each finished job. Defaults to the account webhook set in the dashboard. See Webhooks. |
The response is 202 Accepted with a batches array: one entry per request, in the same order. An entry is either a job or, when that request was invalid or could not be queued, an error object. One invalid request doesn’t reject the rest.
{
"batches": [
{
"id": "0192f7a4-6c1e-7b3a-9d2f-5e8a1c4b7d90",
"status": "queued",
"endpoint": "/v1/search",
"created_at": "2026-09-29T08:00:00Z",
"completed_at": null,
"credits_used": 0,
"poll_url": "https://api.serpkite.com/v1/batches/0192f7a4-6c1e-7b3a-9d2f-5e8a1c4b7d90",
"webhook_url": "https://example.com/hooks/serpkite",
"webhook_status": "pending"
},
{
"error": {
"code": "invalid_request",
"message": "unknown parameter \"type\": use the endpoint path",
"request_id": "req_01J8ZK4M6Q2V7:1"
}
}
]
}
Requests are validated with the same strict rules as realtime calls: an unknown or renamed parameter fails that entry with a message naming the replacement.
Safe retries with Idempotency-Key
A timeout on POST /v1/batches doesn’t tell you whether the jobs were queued. Send an Idempotency-Key header (1 to 255 printable ASCII characters, for example a UUID you store with the batch) and retry with the same key and the same body: for 24 hours SerpKite returns the first response, with Idempotent-Replayed: true, instead of queueing and billing the jobs again.
curl https://api.serpkite.com/v1/batches \
-H "Authorization: Bearer $SERPKITE_API_KEY" \
-H "Idempotency-Key: 5f0c7d52-8a8e-4f7e-9d7b-1b6f0f3c2a91" \
-H "Content-Type: application/json" \
-d '{"endpoint":"search","requests":[{"q":"espresso grinder"}]}'
The same key with a different body gets 422 idempotency_key_reused; a retry that arrives while the first call is still running gets 409 (retry shortly). The SDKs take the key as an option (idempotencyKey, idempotency_key, serpkite.WithIdempotencyKey) and, when it is set, also retry server errors and dropped connections.
Pricing
Batch jobs cost half the realtime price, rounded up to the next 0.001 credit:
| Request | Realtime | Batch |
|---|---|---|
search (1 page) |
1 | 0.5 |
search with num: 100 |
7 | 3.5 |
search with include_content: 3 |
4 | 2 |
The maximum cost of each job is reserved from your balance when it is queued, so a queued job can’t fail later for lack of credits. The jobs of one call are reserved together: if your balance, spend cap, key limit or free daily limit can’t cover all of them, none is queued and each valid entry carries the error (for example insufficient_credits). If a job fails, or costs less than reserved (for example fewer pages came back), the difference is refunded. Queuing is checked against your key limit and spend cap just like a realtime call. The POST /v1/batches call itself counts once against your per-second rate limit, whatever its size; the jobs are paced by the batch lane.
Get the results
Wait with an SDK
The SDKs poll for you: sk.batches.wait(id) in TypeScript, sk.batches.wait(id) in Python and c.Batches.Wait(ctx, id) in Go return the job once it is done or failed. See SDKs.
Poll
Poll GET /v1/batches/{id} (or the job’s poll_url) until status is done or failed. Polling is free.
status |
Meaning |
|---|---|
queued |
Waiting for a worker. |
running |
Being fetched. |
done |
Finished; result holds the same body the realtime endpoint returns (request, results, meta, …). |
failed |
Could not be completed; error has code and message, and the credits were refunded. |
curl https://api.serpkite.com/v1/batches/0192f7a4-6c1e-7b3a-9d2f-5e8a1c4b7d90 \
-H "Authorization: Bearer $SERPKITE_API_KEY"
{
"id": "0192f7a4-6c1e-7b3a-9d2f-5e8a1c4b7d90",
"status": "done",
"endpoint": "/v1/search",
"created_at": "2026-09-29T08:00:00Z",
"completed_at": "2026-09-29T08:03:12Z",
"credits_used": 0.5,
"poll_url": "https://api.serpkite.com/v1/batches/0192f7a4-6c1e-7b3a-9d2f-5e8a1c4b7d90",
"webhook_url": null,
"webhook_status": null,
"error": null,
"result": {
"request": { "endpoint": "search", "engine": "google", "q": "best espresso machine", "country": "us" },
"results": [{ "position": 1, "title": "…", "link": "https://www.example.com/…", "domain": "example.com" }],
"related_searches": [],
"meta": { "request_id": "req_01J8ZK4M6Q2V7", "credits_used": 0.5, "cached": false }
}
}
Poll every few seconds at most; jobs don’t finish faster if you poll harder. For large volumes, prefer webhooks.
Webhooks
Set webhook_url on the batch (or a default webhook URL in the dashboard under Settings) and SerpKite POSTs each finished job to it, always signed with your account’s webhook secret. Then you don’t need to poll at all. Delivery format, signature verification and retries are covered in Webhooks.
Limits and retention
- 1 to 100 requests per
POST /v1/batchescall, all for the same endpoint. - Up to 10,000 queued jobs per account. Beyond that, new batches get
429 rate_limiteduntil some finish. - Jobs target completion within 15 minutes. That is a target for the lane, not a per-job guarantee. Workers take jobs round-robin across accounts, so a large backlog from one account doesn’t hold up another’s jobs.
- A job’s
resultis kept for 24 hours after it finishes, then deleted. The stored request (which contains your query) is deleted as soon as the job completes. - You can list recent jobs and their status in the dashboard.
Related: Batches endpoint, Webhooks, Rank tracking, Credits and billing.
Last updated: 2026-09-29