# Check up to 500 identifiers in one call

`POST /api/v1/entity/batch`

- Authentication: Secret API key as a bearer token
- Billing: Each identifier bills like a single lookup — 1 credit only when a register carried it, free for an unknown, malformed or unsupported identifier, free for a register that is not installed and free for a 7-day repeat — and the call stops cleanly where the balance ends.
- Group: Entity

Runs the single-identifier lookup for every item, in input order, under the exact single-lookup billing rules. Every answer is read from registers synced onto this server, so a full list makes no network call at all. `data.results[]` mirrors the single-identifier response per item and each item's `meta` carries `credits_used` and `cache_hit`.

An item is either a bare identifier string, which is read as a LEI because that is the only type needing no country, or an object with `identifier`, `type` and — for a company number — the `country` whose register it belongs to. Countries are only required per item, never for the list, because a list may mix registers.

The cap is 500, not the 1,000 the IP and phone batches take: those read indexes already held in memory, while each identifier here is a database read, and two when a LEI names a national-register counterpart. It is the same limit a monitored list of identifiers has, and a monitor run walks its list through this same lookup.

`meta.found` is the count this endpoint has that the other batches do not need. Business lookups bill only for an identifier a register actually carried, so a list of 500 unknown identifiers is processed in full and billed nothing, and `processed` on its own would imply a charge that was never made. `credits_used` is `found` less any identifier that was already looked up on this account inside the last seven days.

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 key with a daily credit cap stops the list the same way once the cap is spent, reporting `meta.stopped_reason: "key_spend_cap"`; a call that starts with the cap already spent answers `429 KEY_SPEND_CAP_REACHED` and runs nothing.

A batch counts as one request against the rate limit. As with the single lookup, no registered address is stored or returned.

Every item carries `index`, its zero-based position in the list you sent, and `input`, the identifier exactly as you sent it — the answer carries the register's own spelling of it. A batch stopped early by `stopped_reason` answers only a prefix of the list, so those two are what line an answer up with the row it came from.

## Request body

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `identifiers` | string[] | yes | 1 to 500 identifiers. An item is a LEI string, or an object carrying its own type and country. |
| `identifiers[].identifier` | string | no | At most 64 characters. |
| `identifiers[].type` | string | no | One of: lei, company_number. |
| `identifiers[].country` | string | null | no | Required when `type` is `company_number`. At most 2 characters. |

## Example request

```bash
curl -X POST https://spaw.co/api/v1/entity/batch \
  -H "Authorization: Bearer sk_live_…" \
  -H "Content-Type: application/json" \
  -d '{
  "identifiers": [
    "213800QILIUD4ROSUO03",
    {
      "identifier": "01234567",
      "type": "company_number",
      "country": "GB"
    }
  ]
}'
```

## Responses

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

```json
{
    "success": true,
    "data": {
        "results": [
            {
                "index": 0,
                "input": "213800QILIUD4ROSUO03",
                "data": {
                    "identifier": "213800QILIUD4ROSUO03",
                    "identifier_type": "lei",
                    "found": true,
                    "reason": null,
                    "name": "ACME HOLDINGS LIMITED",
                    "status": "active",
                    "country": "GB",
                    "checksum_valid": true,
                    "registration_status": "ISSUED",
                    "flags": []
                },
                "meta": {
                    "credits_used": 1,
                    "cache_hit": false
                }
            },
            {
                "index": 1,
                "input": "01234567",
                "data": {
                    "identifier": "01234567",
                    "identifier_type": "company_number",
                    "found": true,
                    "reason": null,
                    "name": "ACME HOLDINGS LIMITED",
                    "status": "dissolved",
                    "status_detail": "Dissolved",
                    "country": "GB",
                    "flags": [
                        "dissolved_in_national_register"
                    ]
                },
                "meta": {
                    "credits_used": 1,
                    "cache_hit": false
                }
            },
            {
                "index": 2,
                "input": "5493001KJTIIGC8Y1R99",
                "data": {
                    "identifier": "5493001KJTIIGC8Y1R99",
                    "identifier_type": "lei",
                    "found": false,
                    "reason": "unknown_identifier",
                    "name": null,
                    "checksum_valid": false,
                    "flags": []
                },
                "meta": {
                    "credits_used": 0,
                    "cache_hit": false
                }
            }
        ]
    },
    "meta": {
        "requested": 3,
        "processed": 3,
        "found": 2,
        "credits_used": 2,
        "credits_remaining": 998,
        "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
- `KEY_SPEND_CAP_REACHED` — https://spaw.co/docs/errors/KEY_SPEND_CAP_REACHED
- `RATE_LIMITED` — https://spaw.co/docs/errors/RATE_LIMITED
- `KEY_SCOPE_DENIED` — https://spaw.co/docs/errors/KEY_SCOPE_DENIED
- `EMAIL_NOT_VERIFIED` — https://spaw.co/docs/errors/EMAIL_NOT_VERIFIED

---

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