# Queue a bulk phone lookup run

`POST /api/v1/phone/bulk`

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

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

Rows repeating a number already in the list (as submitted) 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 a phone lookup is stored, the input list and the result file are the only copies and both are deleted with the job. `country` applies to every row; the live carrier-network check is single lookups only.

With a `webhook_url`, the finish (`completed`, `failed`, or `cancelled`) is POSTed there as `{ "event": "bulk_phone_job.finished", "job": { … } }`, signed in `X-Spaw-Signature` with the HMAC-SHA256 of the raw body keyed with `webhook_secret`, exactly like bulk email and IP 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 |
| --- | --- | --- | --- |
| `phones` | string[] | yes | 1 to 100,000 numbers, in any common notation. Each item is a plain string, or an object carrying its own country. |
| `phones[].phone` | string | yes | The number, in any common notation. At most 32 characters. |
| `phones[].country` | string | null | no | This item's own ISO 3166-1 alpha-2 region code, for a number written without a calling code. Wins over the list-level country. |
| `country` | string | null | no | An ISO 3166-1 alpha-2 region code used to parse every number written without a calling code, unless the item names its own. |
| `hlr` | boolean | no | Ask for the live carrier check on every number. While the check is not enabled on the service it answers `hlr_checked` false at no extra cost; when it is, each item that answers is charged the premium credits, and the balance is gated per item before any network query. Default: . |
| `webhook_url` | string | null | no | An https URL to notify when the run finishes. |

## Example request

```bash
curl -X POST https://spaw.co/api/v1/phone/bulk \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "phones": [
    "+1 415 555 0142",
    "(415) 555-0143"
  ],
  "country": "US",
  "webhook_url": "https://example.com/hooks/spaw"
}'
```

## Responses

### 202 — The job was queued.

```json
{
    "success": true,
    "data": {
        "job": {
            "id": 733,
            "status": "queued",
            "total": 2,
            "duplicate_count": 0,
            "processed": 0,
            "valid": 0,
            "invalid": 0,
            "low": 0,
            "medium": 0,
            "high": 0,
            "credits_used": 0,
            "stopped_reason": null,
            "cancel_requested": false,
            "country": "US",
            "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": 733,
            "status": "processing",
            "total": 2,
            "duplicate_count": 0,
            "processed": 1,
            "valid": 1,
            "invalid": 0,
            "low": 0,
            "medium": 0,
            "high": 1,
            "credits_used": 1,
            "stopped_reason": null,
            "cancel_requested": false,
            "country": "US",
            "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, country 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-phone-bulk-job · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
