> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avatcado.com/llms.txt
> Use this file to discover all available pages before exploring further.

# EORI Validation

> Check EORI numbers for the EU, Northern Ireland and Great Britain with one GET request

# EORI Validation

`GET /v1/eori/validate` checks an EORI number against the register that issued it and returns whether it is valid, plus the trader's name and address when the trader published them. It covers the 27 EU member states, Northern Ireland (`XI`) and Great Britain (`GB`).

An EORI number is the customs identifier a business needs to import, export or move goods in transit. It is a separate registration from a VAT number: a company can hold one without the other, so a valid VAT number says nothing about the EORI number. Use [`GET /v1/validate`](https://docs.avatcado.com/api-reference/vat-validation/validate-a-vat-number) for VAT numbers.

## Check a number

```bash curl theme={null}
curl -H "Authorization: Bearer avat_live_your_api_key" \
  "https://api.avatcado.com/v1/eori/validate?eori_number=GB123456789000"
```

```typescript Node.js theme={null}
const res = await fetch(
  "https://api.avatcado.com/v1/eori/validate?eori_number=GB123456789000",
  { headers: { Authorization: "Bearer avat_live_your_api_key" } },
);
const body = await res.json();

if (res.ok) {
  console.log(body.data.valid, body.data.company?.name ?? "no published name");
} else {
  console.log(body.error.code, body.error.message);
}
```

```python Python theme={null}
import requests

res = requests.get(
    "https://api.avatcado.com/v1/eori/validate",
    params={"eori_number": "GB123456789000"},
    headers={"Authorization": "Bearer avat_live_your_api_key"},
    timeout=30,
)
body = res.json()

if res.ok:
    company = body["data"]["company"]
    print(body["data"]["valid"], company["name"] if company else "no published name")
else:
    print(body["error"]["code"], body["error"]["message"])
```

```json theme={null}
{
  "data": {
    "valid": true,
    "eori_number": "GB123456789000",
    "country_code": "GB",
    "company": {
      "name": "EXAMPLE TRADING LTD",
      "address": "1 HIGH STREET, LONDON, SW1A 1AA"
    },
    "requested_at": "2026-11-13T09:19:29.000Z"
  },
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "request_duration_ms": 312,
    "source": "hmrc",
    "source_status": "live",
    "cached": false,
    "stale": false
  }
}
```

> [!NOTE]
>
> The `@avatcado/node` and `avatcado` (Python) SDKs do not cover this endpoint yet. Call it over HTTP as shown above; the response uses the same `{ data, meta }` and `{ error, meta }` envelopes as every other endpoint.

## Query parameters

| Parameter | Required | Description |
| - | - | - |
| `eori_number` | Yes | The number to check. Spaces, dots, dashes and casing are normalized for you: `gb 123.456.789 000` becomes `GB123456789000` |
| `cache` | No | `false` skips the cache and checks the register again. Defaults to `true` |

## Response fields

| Field | Type | Description |
| - | - | - |
| `data.valid` | boolean | Whether the register lists the number as valid |
| `data.eori_number` | string | The normalized number that was checked |
| `data.country_code` | string | The two-letter prefix (`XI` for Northern Ireland) |
| `data.company` | object or null | `name` and `address` from the register. Null when the number is not valid, and null for a valid number whose trader did not publish details |
| `data.requested_at` | string | When the register was asked. For a cached answer, when it was first stored |
| `meta.source` | string | `ec_eos`, `hmrc`, or `test` in test mode |
| `meta.source_status` | string | `live`, `cached` or `unavailable` |
| `meta.cached` | boolean | Whether the answer came from a stored row |
| `meta.stale` | boolean | Whether that stored row is older than the cache window. Only true when the register is down |
| `meta.cached_at` | string | Present when `cached` is true |

## Which register answers

| Prefix | Register | `meta.source` |
| - | - | - |
| `GB` | HMRC, Check an EORI number | `hmrc` |
| `XI` | European Commission EORI validation service | `ec_eos` |
| 27 EU member states | European Commission EORI validation service | `ec_eos` |

`XI` numbers go to the European Commission service because HMRC refuses them and points there. `GB` numbers go to HMRC because the European Commission service answers "not valid" for a GB number that HMRC lists as valid. Both facts were verified against the live registers on 13 November 2026.

Greek EORI numbers start with `GR`, while Greek VAT numbers start with `EL`. The register answers "not valid" for the `EL` spelling of a number it lists as valid under `GR`, so a number sent with `EL` is rewritten to `GR` before the check. The response shows the rewritten number in `data.eori_number`.

## Company details are often null

Both registers publish a trader's name and address only when the trader agreed to publication. A response with `valid: true` and `company: null` is a complete answer: the number is registered, and the register holds no public details for it. Do not treat a null `company` as a failed check.

## Format rules

A number that neither register would accept is rejected with [`invalid_eori_format`](https://docs.avatcado.com/errors/invalid_eori_format) before any register is called. That request does not count against your quota.

| Rule | Example that passes | Example that fails |
| - | - | - |
| Two-letter prefix, then 1 to 15 letters or digits | `DE123456789012345` | `DE1234567890123456` (16 after the prefix) |
| Prefix is an EU member state, `XI` or `GB` | `NL123456789` | `US123456789` |
| `GB` is followed by 12 to 15 digits | `GB123456789000` | `GB123456789` |

## Caching and register outages

Answers are stored and served from the cache for 25 days, the same window as VAT validations. A cached answer has `meta.source_status: "cached"`.

No country has a second EORI register, so an outage is covered by stored answers only:

* **A stored answer exists**: it is served with `meta.source_status: "unavailable"` and `meta.stale: true`. Check `meta.cached_at` to see how old it is.
* **No stored answer exists**: the request ends in a [`upstream_unavailable`](https://docs.avatcado.com/errors/upstream_unavailable) 503 with a `Retry-After` header.
* **`cache=false`**: the request answers with a fresh check or a 503. A stored answer is never served.

Before giving up, the API tries the register up to three times.

## Quota

| Outcome | Counts against the monthly quota |
| - | - |
| 200, live or cached | Yes, 1 request |
| 200, stored answer during an outage | Yes, 1 request |
| 422 `invalid_eori_format` | No |
| 503 `upstream_unavailable` | No |

The endpoint is available on every plan, Free included. [Burst limits](https://docs.avatcado.com/rate-limits.md) apply as on the VAT endpoints.

## Test mode

With an `avat_test_` key the endpoint answers from fixed numbers. No register is called, nothing is stored and no quota is used.

| EORI number | Scenario | Status |
| - | - | - |
| `NL111111111` | Valid, with company details (Test B.V., Amsterdam, Netherlands) | 200 |
| `GB111111111111` | Valid GB number, with company details (Test Ltd, London, United Kingdom) | 200 |
| `NL222222222` | Valid, `company: null` | 200 |
| `NL000000000` | Not valid | 200 |
| `NL999999999` | Register down, `upstream_unavailable` | 503 |

Any other well-formed number returns `valid: true` with a generic "Test Company". Format rules still apply in test mode.

## Errors

| Status | Code | When |
| - | - | - |
| 400 | [`missing_parameter`](https://docs.avatcado.com/errors/missing_parameter) | `eori_number` is absent or empty |
| 401 | [`unauthorized`](https://docs.avatcado.com/errors/unauthorized) | Missing or invalid API key |
| 422 | [`invalid_eori_format`](https://docs.avatcado.com/errors/invalid_eori_format) | The number breaks a format rule above |
| 422 | [`validation_error`](https://docs.avatcado.com/errors/validation_error) | `cache` is something other than `true` or `false` |
| 429 | [`rate_limit_exceeded`](https://docs.avatcado.com/errors/rate_limit_exceeded) | Monthly quota used up |
| 429 | [`burst_limit_exceeded`](https://docs.avatcado.com/errors/burst_limit_exceeded) | Too many requests this minute |
| 503 | [`upstream_unavailable`](https://docs.avatcado.com/errors/upstream_unavailable) | The register is down and no stored answer exists |

Errors echo the normalized `eori_number` you submitted, so your logs record which number an attempt was for:

```json theme={null}
{
  "error": {
    "code": "upstream_unavailable",
    "message": "The HMRC EORI service is unavailable",
    "docs_url": "https://docs.avatcado.com/errors/upstream_unavailable",
    "eori_number": "GB123456789000"
  },
  "meta": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000"
  }
}
```

## Limits of this endpoint

* One number per request. There is no batch or async variant for EORI numbers.
* Neither register supports a search by company name. A check needs the number.
* Webhooks do not report changes to an EORI number's status.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.