{
  "openapi": "3.1.0",
  "info": {
    "title": "TaxID VAT Validation API",
    "version": "1.0.0",
    "summary": "Validate VAT numbers against 29 live registries over REST.",
    "description": "REST/JSON validation of VAT and tax identification numbers — all 27 EU member states via VIES, plus the United Kingdom and Norway. Wraps the EU VIES SOAP service with caching, local format pre-checks, and a distinct status for registry downtime so an outage never reads as an invalid number. Countries without a reachable registry are validated by format and checksum.",
    "contact": {
      "name": "TaxID",
      "url": "https://www.taxid.dev/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.taxid.dev/terms"
    },
    "termsOfService": "https://www.taxid.dev/terms"
  },
  "servers": [
    {
      "url": "https://www.taxid.dev/api",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "API documentation",
    "url": "https://www.taxid.dev/docs"
  },
  "tags": [
    {
      "name": "Validation",
      "description": "Check a VAT number against its registry."
    },
    {
      "name": "Rates",
      "description": "Standard and reduced VAT/GST rates by country."
    },
    {
      "name": "Service",
      "description": "Availability of the API and its upstreams."
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/v1/validate/{country_code}/{vat_number}": {
      "get": {
        "tags": [
          "Validation"
        ],
        "operationId": "validateVatNumber",
        "summary": "Validate one VAT number",
        "description": "The number may be given with or without its country prefix; sending the prefix twice is the most common cause of a wrong answer, so it is stripped for you. All 27 EU member states are checked against VIES.",
        "parameters": [
          {
            "name": "country_code",
            "in": "path",
            "required": true,
            "description": "ISO 3166-1 alpha-2 code. Case-insensitive.",
            "schema": {
              "type": "string",
              "enum": [
                "AT",
                "BE",
                "BG",
                "CY",
                "CZ",
                "DE",
                "DK",
                "EE",
                "EL",
                "ES",
                "FI",
                "FR",
                "HR",
                "HU",
                "IE",
                "IT",
                "LT",
                "LU",
                "LV",
                "MT",
                "NL",
                "PL",
                "PT",
                "RO",
                "SE",
                "SI",
                "SK",
                "GB",
                "AU",
                "NO",
                "CH",
                "CA",
                "IN",
                "SG",
                "NZ",
                "ZA",
                "AE",
                "MX",
                "JP",
                "IS",
                "MY",
                "KR",
                "HK",
                "BR",
                "TR",
                "TH",
                "ID"
              ]
            }
          },
          {
            "name": "vat_number",
            "in": "path",
            "required": true,
            "description": "The number, with or without the country prefix. Spaces are ignored.",
            "schema": {
              "type": "string",
              "example": "DE123456789"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The registry answered, or explicitly did not.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationResult"
                }
              }
            }
          },
          "400": {
            "description": "Unsupported country code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Monthly plan quota exhausted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Daily upstream capacity reached; retry after 00:00 UTC.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/validate": {
      "post": {
        "tags": [
          "Validation"
        ],
        "operationId": "validateVatNumbersBatch",
        "summary": "Validate up to 25 VAT numbers",
        "description": "Each item is resolved independently: one failure does not fail the batch, and failed items carry their own error object instead of a result.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "vats"
                ],
                "properties": {
                  "vats": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 25,
                    "items": {
                      "type": "object",
                      "required": [
                        "country",
                        "vat"
                      ],
                      "properties": {
                        "country": {
                          "type": "string",
                          "enum": [
                            "AT",
                            "BE",
                            "BG",
                            "CY",
                            "CZ",
                            "DE",
                            "DK",
                            "EE",
                            "EL",
                            "ES",
                            "FI",
                            "FR",
                            "HR",
                            "HU",
                            "IE",
                            "IT",
                            "LT",
                            "LU",
                            "LV",
                            "MT",
                            "NL",
                            "PL",
                            "PT",
                            "RO",
                            "SE",
                            "SI",
                            "SK",
                            "GB",
                            "AU",
                            "NO",
                            "CH",
                            "CA",
                            "IN",
                            "SG",
                            "NZ",
                            "ZA",
                            "AE",
                            "MX",
                            "JP",
                            "IS",
                            "MY",
                            "KR",
                            "HK",
                            "BR",
                            "TR",
                            "TH",
                            "ID"
                          ]
                        },
                        "vat": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              },
              "example": {
                "vats": [
                  {
                    "country": "DE",
                    "vat": "123456789"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One entry per input, in the order given.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "results": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/ValidationResult"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "error": {
                                "type": "object",
                                "description": "Present instead of a result when this item failed.",
                                "properties": {
                                  "code": {
                                    "type": "string"
                                  },
                                  "message": {
                                    "type": "string"
                                  }
                                }
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Malformed body, empty batch, or more than 25 items.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Monthly plan quota exhausted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/ai/validate/{country_code}/{vat_number}": {
      "get": {
        "tags": [
          "Validation"
        ],
        "operationId": "validateVatNumberKeyless",
        "summary": "Validate one VAT number without an API key",
        "description": "Keyless endpoint for agents and quick checks, rate-limited per IP. Every response carries an `upgrade` object pointing at a free API key. When the day's shared registry allowance is spent it answers `status: \"format_only\"` with `format_valid`, rather than failing.",
        "security": [],
        "parameters": [
          {
            "name": "country_code",
            "in": "path",
            "required": true,
            "description": "ISO 3166-1 alpha-2 code. Case-insensitive.",
            "schema": {
              "type": "string",
              "enum": [
                "AT",
                "BE",
                "BG",
                "CY",
                "CZ",
                "DE",
                "DK",
                "EE",
                "EL",
                "ES",
                "FI",
                "FR",
                "HR",
                "HU",
                "IE",
                "IT",
                "LT",
                "LU",
                "LV",
                "MT",
                "NL",
                "PL",
                "PT",
                "RO",
                "SE",
                "SI",
                "SK",
                "GB",
                "AU",
                "NO",
                "CH",
                "CA",
                "IN",
                "SG",
                "NZ",
                "ZA",
                "AE",
                "MX",
                "JP",
                "IS",
                "MY",
                "KR",
                "HK",
                "BR",
                "TR",
                "TH",
                "ID"
              ]
            }
          },
          {
            "name": "vat_number",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A validation result, or a format-only result under load.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ValidationResult"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "format_valid": {
                          "type": "boolean"
                        },
                        "notice": {
                          "type": "string"
                        },
                        "upgrade": {
                          "type": "object",
                          "properties": {
                            "message": {
                              "type": "string"
                            },
                            "free_api_key_url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "docs_url": {
                              "type": "string",
                              "format": "uri"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Unsupported country code.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Per-IP rate limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/rates": {
      "get": {
        "tags": [
          "Rates"
        ],
        "operationId": "listVatRates",
        "summary": "List VAT/GST rates for every country covered",
        "security": [],
        "responses": {
          "200": {
            "description": "Every rate entry.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "rates": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/VatRate"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/rates/{country_code}": {
      "get": {
        "tags": [
          "Rates"
        ],
        "operationId": "getVatRate",
        "summary": "VAT/GST rates for one country",
        "security": [],
        "parameters": [
          {
            "name": "country_code",
            "in": "path",
            "required": true,
            "description": "ISO 3166-1 alpha-2 code. Case-insensitive.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VatRate"
                }
              }
            },
            "description": "Rates."
          },
          "400": {
            "description": "No rate data for that country.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/health": {
      "get": {
        "tags": [
          "Service"
        ],
        "operationId": "getHealth",
        "summary": "API and upstream availability",
        "security": [],
        "responses": {
          "200": {
            "description": "Operational or degraded.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "operational",
                        "degraded",
                        "down"
                      ]
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "components": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "string"
                      }
                    },
                    "latency_ms": {
                      "type": "object",
                      "additionalProperties": {
                        "type": "integer"
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "At least one component is down."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Create a key at https://www.taxid.dev/signup — 100 validations a month, no card."
      }
    },
    "schemas": {
      "ValidationResult": {
        "type": "object",
        "required": [
          "valid",
          "status",
          "vat_number",
          "country_code",
          "request_date"
        ],
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "True only when the registry recognises the number as active."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "invalid",
              "service_unavailable"
            ],
            "description": "Branch on this, not on `valid`. `service_unavailable` means the registry did not answer — it is not a verdict on the number, and treating it as one rejects real customers during someone else's outage."
          },
          "vat_number": {
            "type": "string",
            "description": "The number as validated: uppercase, no spaces, no country prefix."
          },
          "country_code": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          },
          "company_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null where the member state withholds it — Germany and Spain routinely do. A null name alongside valid:true is a complete answer, not a partial one."
          },
          "company_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "request_date": {
            "type": "string",
            "description": "When the registry produced the answer."
          },
          "source": {
            "type": "string",
            "example": "VIES"
          },
          "vat_registered": {
            "type": "boolean",
            "description": "Present only for registries that report it separately from validity."
          },
          "cached": {
            "type": "boolean",
            "description": "Served from cache (24h TTL) rather than the registry."
          },
          "request_id": {
            "type": "string",
            "example": "req_01j5k8n2p9q4r7s3t6v8"
          }
        }
      },
      "VatRate": {
        "type": "object",
        "properties": {
          "country_code": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "country_name": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "tax_type": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "standard_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "reduced_rates": {
            "type": [
              "array",
              "null"
            ],
            "items": {}
          },
          "reduced_rate_categories": {
            "type": [
              "array",
              "null"
            ],
            "items": {}
          },
          "super_reduced_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "parking_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "registration_threshold": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "amount": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "currency": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "note": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The condition the amount is subject to. Often the deciding detail."
              }
            }
          },
          "currency": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "last_updated": {
            "type": [
              "string",
              "number",
              "null"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "code",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "authentication",
                  "rate_limit",
                  "service_unavailable",
                  "api_error"
                ]
              },
              "code": {
                "type": "string",
                "example": "country_not_supported"
              },
              "message": {
                "type": "string"
              },
              "doc_url": {
                "type": "string",
                "format": "uri"
              }
            }
          }
        }
      }
    }
  }
}