Skip to content
Gridlode
Docs · Bulk jobs

Bulk jobs and webhooks

A bulk job runs one engine over a list of inputs in the background. Each item is billed like a normal request, failed items are free, and the job stops cleanly if you run out of credits or hit your spend cap. You can also create jobs without code in the dashboard.

Create a job

Send either inputs (a list of values plus the parameter field they fill) or items (a list of full parameter objects). params is merged into every item. Up to 10,000 items per job.

POST /v1/tasks
curl -X POST "https://api.gridlode.com/v1/tasks" \
  -H "Authorization: Bearer $GRIDLODE_KEY" -H "Content-Type: application/json" \
  -d '{
    "engine": "domain.tech",
    "inputs": ["shopify.com", "wordpress.org", "vercel.com"],
    "field": "domain",
    "name": "Competitor stacks",
    "webhook_url": "https://example.com/hooks/gridlode"
  }'

The response contains the job, the estimated credits and, when you pass a webhook URL, a one-time webhook_secret.

Check progress and download results

  • GET /v1/tasks/{id}: status (queued, running, done, failed, cancelled), counts and credits.
  • GET /v1/tasks/{id}/results?format=csv: one row per input with flattened result columns. Also format=json and format=ndjson. Add offset and limit to page through large jobs; JSON responses include a pagination object with next_offset.
  • POST /v1/tasks/{id}/cancel: stops remaining items (they are not charged).
  • GET /v1/tasks?limit=50&offset=0: your jobs, newest first, with pagination.has_more.

Webhooks

When a job ends we POST {"event":"task.completed","task":{...}} to your URL (https only, public addresses only), retrying up to three times. The event is task.completed, task.failed (for example when credits run out) or task.cancelled. Verify the X-Gridlode-Signature header: t=timestamp,v1=hex, where v1 = HMAC_SHA256(webhook_secret, t + "." + raw_body).

Verify (Python)
import hmac, hashlib, time

def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])