# Validate up to 1,000 numbers in one call

`POST /api/v1/phone/batch`

- Authentication: Secret API key as a bearer token
- Billing: Each number bills like a single lookup: 1 credit for a fresh valid answer; invalid numbers and 7-day repeats are free. The call stops cleanly where the balance ends.
- Group: Phone

Runs the single-number lookup for every item, in input order, under the exact single-lookup billing rules. Every answer comes from local metadata and the synced regulator tables, so a full batch of 1,000 takes well under a second. `data.results[]` mirrors the single-number response per item and each item's `meta` carries `credits_used` and `cache_hit`.

Repeats inside the list are looked up once and answer as cache hits. The list is walked in order and stops where the balance would run out: running out of credits mid-batch returns the paid partial results with `meta.stopped_reason: "insufficient_credits"` and `processed` lower than `requested`, never discarding billed work. Only a batch whose first lookup is refused answers the typed `402`. A string that is not a number is an answer (`valid: false`), not a validation error.

A batch counts as one request against the rate limit. `country` applies to every number in the list that does not name its own; the live carrier-network check is available on single lookups only.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `phones` | string[] | yes | 1 to 1,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: . |

## Example request

```bash
curl -X POST https://spaw.co/api/v1/phone/batch \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "phones": [
    "(415) 555-0142",
    {
      "phone": "020 7946 0018",
      "country": "GB"
    },
    "not a number"
  ],
  "country": "US"
}'
```

## Responses

### 200 — One result per processed number, plus the batch totals.

```json
{
    "success": true,
    "data": {
        "results": [
            {
                "data": {
                    "valid": true,
                    "reason": null,
                    "e164": "+14155550142",
                    "country": "US",
                    "line_type": "fixed_line_or_mobile",
                    "is_fictional": true,
                    "risk_score": 80,
                    "risk_level": "high"
                },
                "meta": {
                    "credits_used": 1,
                    "cache_hit": false
                }
            },
            {
                "data": {
                    "valid": false,
                    "reason": "unassigned_block",
                    "e164": "+442079460018",
                    "country": "GB",
                    "block_status": "unassigned",
                    "is_fictional": true,
                    "risk_score": null,
                    "risk_level": null
                },
                "meta": {
                    "credits_used": 0,
                    "cache_hit": false
                }
            },
            {
                "data": {
                    "valid": false,
                    "reason": "not_a_number",
                    "e164": null,
                    "country": null,
                    "risk_score": null,
                    "risk_level": null
                },
                "meta": {
                    "credits_used": 0,
                    "cache_hit": false
                }
            }
        ]
    },
    "meta": {
        "requested": 3,
        "processed": 3,
        "credits_used": 1,
        "credits_remaining": 9,
        "stopped_reason": null,
        "request_id": "req_01m1kgdrtqdvwnks99vfgx2rcw"
    }
}
```

### 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"
    }
}
```

### 402 — The balance is empty. The lookup did not run.

```json
{
    "success": false,
    "error": {
        "code": "INSUFFICIENT_CREDITS",
        "message": "Your credit balance is empty.",
        "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
- `INSUFFICIENT_CREDITS` — https://spaw.co/docs/errors/INSUFFICIENT_CREDITS
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED

---

Canonical page: https://spaw.co/docs/api/lookup-phone-batch · OpenAPI document: https://spaw.co/openapi.json · All endpoints: https://spaw.co/docs/api
