# Queue a bulk IP lookup run

`POST /api/v1/ip/bulk`

- Authentication: Secret API key as a bearer token
- Billing: Each row bills like a single lookup as it is processed; repeats inside the run and 7-day repeats are free.
- Group: IP

Queues up to 100,000 addresses and processes them in the background. Creation answers `202` with the job's id and status. Poll `GET /api/v1/ip/bulk/{jobId}` for progress, then download the result CSV from `GET /api/v1/ip/bulk/{jobId}/results`: one row per input address with every response field as a column. Jobs and their files are kept for 30 days.

Rows repeating an address already in the list are looked up once and counted in `duplicate_count`. A run that exhausts the balance stops with `stopped_reason: "insufficient_credits"` and keeps everything answered so far. Because nothing about an IP lookup is stored, the input list and the result file are the only copies and both are deleted with the job.

With a `webhook_url`, the finish (`completed`, `failed`, or `cancelled`) is POSTed there as `{ "event": "bulk_ip_job.finished", "job": { … } }`, signed in `X-Spaw-Signature` with the HMAC-SHA256 of the raw body keyed with `webhook_secret`, exactly like bulk email runs. Send an `Idempotency-Key` header to make creation safe to retry: a repeat of the same request answers the job the first attempt created, as `200` with `Idempotent-Replayed: true`; the same key with a different request answers `409 IDEMPOTENCY_KEY_REUSED`.

## Parameters

| Name | In | Type | Required | Description |
| --- | --- | --- | --- | --- |
| `Idempotency-Key` | header | string | no | A value unique to this request, so a retry after a timeout answers the same job instead of queueing a second one. 1 to 128 characters. |

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `ips` | string[] | yes | 1 to 100,000 IPv4 or IPv6 addresses. |
| `webhook_url` | string | null | no | An https URL to notify when the run finishes. |
| `privacy` | boolean | null | no | Keep nothing about these lookups; every address is billed as a fresh lookup. |

## Example request

```bash
curl -X POST https://spaw.co/api/v1/ip/bulk \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "ips": [
    "8.8.8.8",
    "103.124.165.2"
  ],
  "webhook_url": "https://example.com/hooks/spaw"
}'
```

## Responses

### 202 — The job was queued.

```json
{
    "success": true,
    "data": {
        "job": {
            "id": 512,
            "status": "queued",
            "total": 2,
            "duplicate_count": 0,
            "processed": 0,
            "low": 0,
            "medium": 0,
            "high": 0,
            "credits_used": 0,
            "stopped_reason": null,
            "cancel_requested": false,
            "webhook_status": null,
            "created_at": "2026-09-04T10:12:44+00:00",
            "finished_at": null,
            "webhook_secret": "8fJ2…40 characters…Qk1"
        }
    }
}
```

### 200 — A retry with an `Idempotency-Key` already used for this exact request: the job the first attempt created, with `Idempotent-Replayed: true`.

```json
{
    "success": true,
    "data": {
        "job": {
            "id": 512,
            "status": "processing",
            "total": 2,
            "duplicate_count": 0,
            "processed": 1,
            "low": 0,
            "medium": 1,
            "high": 0,
            "credits_used": 1,
            "stopped_reason": null,
            "cancel_requested": false,
            "webhook_status": null,
            "created_at": "2026-09-04T10:12:44+00:00",
            "finished_at": null
        }
    }
}
```

### 401 — The key is missing, malformed, or revoked.

```json
{
    "success": false,
    "error": {
        "code": "UNAUTHENTICATED",
        "message": "Provide a valid API key as a bearer token.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 409 — The `Idempotency-Key` was already used for a different list or webhook URL.

```json
{
    "success": false,
    "error": {
        "code": "IDEMPOTENCY_KEY_REUSED",
        "message": "This Idempotency-Key was already used for a different request.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 422 — The request body could not be validated; `error.errors` lists the fields.

```json
{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

### 429 — Over 5 requests per second for the key. Retry after the limit resets.

```json
{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}
```

## Error codes

- `UNAUTHENTICATED` — https://spaw.co/docs/errors/UNAUTHENTICATED
- `VALIDATION_FAILED` — https://spaw.co/docs/errors/VALIDATION_FAILED
- `IDEMPOTENCY_KEY_REUSED` — https://spaw.co/docs/errors/IDEMPOTENCY_KEY_REUSED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED

---

Canonical page: https://spaw.co/docs/api/create-ip-bulk-job · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
