Validate Greek VAT Numbers (ΑΦΜ) in Node.js

14 Oct 2026

Validate Greek VAT Numbers (ΑΦΜ) in Node.js

A Greek VAT number doesn’t carry the ISO country code you’d expect. It’s EL followed by 9 digits, not GR – and that one substitution is where most format bugs here come from. Below: the actual format, why EL and not GR, format validation in Node.js, and what a check response does and doesn’t carry for Greece. This post covers the Node.js/JavaScript implementation; for the general EU-wide behavior of the endpoint, see the Greece VAT API reference.

What a Greek VAT number looks like

The Greek VAT ID is:

  • EL — the VIES country prefix (not the ISO code)
  • NNNNNNNNN — 9 digits, the ΑΦΜ (Αριθμός Φορολογικού Μητρώου, literally ‘tax registry number’) – Greece’s general tax identification number

So EL123456789 is EL plus the 9-digit ΑΦΜ 123456789 – no separate VAT-specific sequence, no establishment or branch suffix. The whole string after EL is the ΑΦΜ as issued.

Why EL, not GR

Every other EU member state uses its ISO 3166-1 alpha-2 code as its VAT prefix – DE, FR, PL. Greece is the one that breaks: EL is not an ISO 3166 country code at all, it’s the EU’s own country-code exception for Greece (from Ελλάδα, ‘Hellas’), used across EU institutional contexts instead of the ISO 3166-1 code GR – and VIES follows that exception.

That split carries into vatnode’s own validation: GR is a rates-only code, never a valid VAT prefix, and only the EL form is recognized. Send GR123456789 to the API and you get back INVALID_FORMAT (400) – the generic malformed-ID error, firing because GR never resolves to a country code for VAT-ID purposes.

Rates data goes the other way: /v1/rates keys Greece under the ISO code GR, not EL – though /v1/rates/EL answers the same, since the endpoint accepts both and normalizes internally. If you slice a country code off a validated VAT ID to look up a rate, you’re holding EL and need to map it to GR first. A response from /v1/vat/EL123456789 already reports "countryCode": "GR", so that mapping is done for you on the way out – it only matters if your own code re-derives the country from the raw VAT string instead.

Step 1: format validation in Node.js

Reject malformed input before any network call. The regex is a straightforward shape check – EL plus exactly 9 digits, nothing more:

const EL_VAT_PATTERN = /^EL\d{9}$/

function normaliseGreekVatId(
  input: string,
  opts: { mode?: 'strict' | 'lenient' } = {}
): string | null {
  let cleaned = input.replace(/[\s.\-]/g, '').toUpperCase()
  // Lenient mode: prepend EL when the user typed only the 9-digit AFM
  if (opts.mode === 'lenient' && /^\d{9}$/.test(cleaned)) {
    cleaned = `EL${cleaned}`
  }
  return EL_VAT_PATTERN.test(cleaned) ? cleaned : null
}

// Strict (default) — API consumers should send a fully-qualified ID
normaliseGreekVatId('EL 123456789') // → 'EL123456789'
normaliseGreekVatId('123456789') // → null

// Lenient — for checkout forms where customers paste the AFM
normaliseGreekVatId('123456789', { mode: 'lenient' }) // → 'EL123456789'

// Strict — GR is never a valid VAT prefix, in either mode
normaliseGreekVatId('GR123456789') // → null

There’s no checksum section here: vatnode’s validator checks shape only – EL plus 9 digits, no check-digit arithmetic. A 9-digit string matching the pattern is well-formed input, nothing more; the authoritative answer still comes from VIES. Paste a real Greek number into the VAT checker to see what VIES returns before writing any code.

Step 2: calling the API

Format-valid input goes to VIES, which routes an EL query to the Greek tax administration and relays back one of:

  • A valid/invalid answer, typically with trader name and address attached for Greece – treat this as commonly present, not guaranteed on every request.
  • MS_UNAVAILABLE — the Greek node is temporarily unreachable.
  • SERVICE_UNAVAILABLE — VIES itself is degraded.
  • A timeout, if the Greek node doesn’t respond in time.

The vatnode API surfaces the last three as its own VIES_UNAVAILABLE or UPSTREAM_TIMEOUT codes (see Error handling below), not VIES’s raw fault strings. None of this is specific to Greek numbers – every member-state node can go down. The VIES downtime guide covers the failure modes worth designing around.

Why Greece has no national fallback or enrichment

For some member states, vatnode can fall back to the national VAT register when the VIES node goes down, and separately enrich a valid result by calling a registry for extra company data. Greece has neither: no fallback source, so a VIES EL outage reflects the outage directly – the same reality the Irish VAT number guide walks through for a country in the same position – and no registry call to enrich a valid result.

What a valid check does carry is taxId and taxIdName, but that’s not enrichment – it’s read straight off the VAT number itself, because the suffix after EL is the ΑΦΜ, with nothing to look up externally. registryCode and registryCodeName stay null: Greece’s company registry number, the G.E.MI. number (Γενικό Εμπορικό Μητρώο), is separate from the ΑΦΜ and nothing in a check derives or fetches it. Current per-country coverage is documented at /docs/coverage.

Full working example with the vatnode API

const res = await fetch('https://api.vatnode.dev/v1/vat/EL123456789', {
  headers: { Authorization: `Bearer ${process.env.VATNODE_API_KEY}` },
})

if (!res.ok) {
  const { error } = await res.json()
  throw new Error(`VAT check failed: ${error.code}`)
}

const data = await res.json()
// {
//   "valid": true,
//   "vatId": "EL123456789",
//   "countryCode": "GR",
//   "countryName": "Greece",
//   "companyName": "Example AE",             // typically present, not guaranteed
//   "companyAddress": "1 Example Street, Athens", // same — typically present, not guaranteed
//   "registryCode": null,
//   "registryCodeName": null,
//   "taxId": "123456789",
//   "taxIdName": "ΑΦΜ",
//   "source": "VIES",
//   "consultationNumber": "WAPIAAAAX...", // only when a requester VAT is set (dashboard → Account details)
//   "checkId": "019d2a89-a5d9-7b97-b710-57b84604de2b",
//   "verifiedAt": "2026-10-14T08:30:00.000Z"
// }

EL123456789 is a synthetic example built to demonstrate the format and response shape – verify a real number against VIES before treating it as a fixture. countryCode comes back GR, even though the VAT ID itself carries EL – filter and group by countryCode, not by re-parsing the EL prefix off vatId. source always reads "VIES" here; there’s no EL fallback to branch on.

For a sole trader, the ΑΦΜ is the person’s own tax number and companyName is their name, so treat both as personal data under the GDPR: store them only where you need them, keep them out of logs and analytics, and apply the same retention rules as other customer data. taxId carries the ΑΦΜ, but registryCode stays null – don’t expect a G.E.MI. number to populate.

If valid is false, that’s VIES saying the number isn’t currently registered for intra-EU VAT – a number can be valid domestically and still come back invalid from VIES. This article is informational, not tax advice; confirm reverse-charge treatment with a qualified adviser. The consultationNumber, when present, is the VIES-issued reference covered in the audit trail guide – returned only when your account has a requester VAT number set in dashboard Account details.

Get a free API key at vatnode.dev/register – 100 requests/month, no card – full field reference in the API docs. The Node.js VAT validation guide covers the storage schema across every member state.

Error handling

The vatnode API returns explicit error codes:

  • INVALID_FORMAT (400) – the string isn’t a well-formed EL VAT ID (including a GR-prefixed string). Your input; surface it inline, don’t retry.
  • INVALID_REQUESTER (422) – your configured requester VAT number was rejected by VIES. Fix it in dashboard Account details.
  • RATE_LIMITED (429) – you’ve spent your quota. Retryable on a longer horizon, or see pricing for plans with more requests.
  • VIES_UNAVAILABLE (503) – the Greek node (or VIES itself) is down. Transient – queue a retry, never mark the number invalid.
  • VIES_ERROR (502) / UPSTREAM_TIMEOUT (504) – transient upstream faults. Retry with backoff.
  • VIES_ERROR (403) – VIES reports the VAT ID or vatnode’s IP as blocked (VAT_BLOCKED / IP_BLOCKED). Not a verdict on the number, but not transient either – don’t retry in a loop; alert and review.
  • AUDIT_WRITE_FAILED (502) – the check ran but its audit row couldn’t be stored, so no checkId was issued. Not billed, safe to retry.
  • INTERNAL_ERROR (500) – retry, alert if it persists.

With no EL fallback to fill the gap, retry with exponential backoff (500ms, 2s, 5s), then queue the check for a background re-run rather than blocking checkout. Full code reference at /docs/errors; retry table in handling VIES error codes.

FAQ

Is a Greek VAT number EL or GR?

The VAT ID prefix is EL, not GR. EL isn’t an ISO 3166 country code – it’s the EU’s own country-code exception for Greece, used across EU institutional contexts instead of the ISO 3166-1 code GR – and the vatnode API follows that exception, rejecting a GR-prefixed string as INVALID_FORMAT. GR still shows up elsewhere, because it’s the ISO code Greece is keyed under in country and rates data. A developer working across both needs to map EL to GR and back, not assume one code everywhere.

Is the ΑΦΜ the same as the VAT number?

Yes. Greece doesn’t layer a separate VAT-specific identifier on top of the tax number – the ΑΦΜ (Αριθμός Φορολογικού Μητρώου, literally ‘tax registry number’) is the 9-digit string that, prefixed with EL, becomes the VAT ID. There’s no second number to look up.

Does vatnode have a national fallback or enrichment for Greece?

No. There is no national tax-authority or company-registry integration wired into vatnode’s fallback or enrichment path for EL numbers. When VIES EL is unavailable there’s no second source to fall back to – the check reflects the outage. A valid check’s taxId and taxIdName come straight from the VAT number itself, not from a registry call. Current per-country coverage is documented at /docs/coverage.

Validate Greek VAT numbers without owning the EL/GR mapping yourself

A single call runs the VIES EL check, maps the response to the ISO countryCode and the ΑΦΜ-derived taxId, and returns a stable response shape – with the consultation number for your audit trail when a requester VAT is configured. Free plan, 100 requests/month.

Get a free API key · API reference · Greece VAT API reference