Home / VAT API / Integrations / Ruby on Rails
Ruby on Rails EU VAT Validation API
Validate EU VAT numbers in Ruby on Rails with the TaxID API. Wrap it in a service object or a custom ActiveModel validator so a customer's VAT number is checked against VIES before an order is zero-rated — with a background re-check via Active Job and Sidekiq for subscriptions.
Quick start
CURL
curl -H "Authorization: Bearer YOUR_API_KEY" \ https://www.taxid.dev/api/v1/validate/DE/DE123456789
Code example
Full integration example including error handling for service_unavailable responses from VIES.
Ruby (service object)
# app/services/vat_validator.rb
require "net/http"
require "json"
class VatValidator
Result = Struct.new(:valid, :status, :company_name, keyword_init: true)
def self.check(country, number)
uri = URI("https://www.taxid.dev/api/v1/validate/#{country}/#{number}")
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{Rails.application.credentials.taxid_api_key}"
res = Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 10) do |http|
http.request(req)
end
data = JSON.parse(res.body)
Result.new(valid: data["valid"], status: data["status"],
company_name: data["company_name"])
end
endRuby (ActiveModel validator)
# app/validators/vat_number_validator.rb
class VatNumberValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return if value.blank?
result = VatValidator.check(value[0, 2].upcase, value)
# Only a definitive 'invalid' fails; a VIES outage passes through
if result.status == "invalid"
record.errors.add(attribute, "is not a registered EU VAT number")
end
end
end
# app/models/customer.rb
# validates :vat_number, vat_number: true, allow_blank: trueAPI response
The TaxID API returns a consistent JSON response for every validation:
{
"valid": true,
"status": "active",
"country_code": "DE",
"vat_number": "DE123456789",
"company_name": "Example GmbH",
"company_address": "Musterstraße 1, 10115 Berlin",
"request_date": "2026-05-10T00:00:00.000Z",
"cached": false,
"request_id": "req_01j..."
}activeVAT number is valid and the business is registered
invalidVAT number format is wrong or not registered in VIES
service_unavailableVIES or the national authority is temporarily down — retry later, do not silently zero-rate
Implementation steps
- 1
Get a free TaxID API key
Sign up at taxid.dev/signup for a free key with 100 validations/month. Store it with Rails encrypted credentials (bin/rails credentials:edit) or an ENV var, and read it via Rails.application.credentials.taxid_api_key so it never appears in your codebase.
- 2
Write a VatValidator service object
Add app/services/vat_validator.rb with a class method that uses Net::HTTP (or Faraday if you already depend on it) to GET https://www.taxid.dev/api/v1/validate/#{country}/#{number} with an Authorization bearer header. Parse the JSON and return a small result struct so callers work with valid?, status, and company_name.
- 3
Enforce it with a custom validator
Create a VatNumberValidator < ActiveModel::EachValidator that calls the service and adds an error when status is 'invalid'. Declare validates :vat_number, vat_number: true on your model so an invalid number fails save and surfaces in the form. Treat 'service_unavailable' as valid so a VIES outage does not block sign-ups.
- 4
Store the validated company data
On a status of 'active', save company_name, company_address, and validated_at to the record. Keeping the snapshot gives you audit-ready proof and lets you skip re-validation for numbers already checked within the cache window, keeping form submissions fast.
- 5
Re-validate with Active Job and Sidekiq
Schedule a recurring job (sidekiq-cron or solid_queue) that re-validates customers flagged for reverse-charge monthly. Registrations can be cancelled between invoices, and audits expect per-invoice evidence. On 'service_unavailable', re-enqueue the job with a delay instead of clearing the reverse-charge flag.
Frequently asked questions
How do I add a custom VAT validator in Rails?
Subclass ActiveModel::EachValidator (e.g. VatNumberValidator) and implement validate_each(record, attribute, value) to call your TaxID service, adding record.errors when the number is not active. Then use validates :vat_number, vat_number: true on the model. Rails runs it during save so invalid numbers never persist.
Should validation run in the request or a background job?
Validate synchronously at the point of capture — checkout or onboarding — so the customer gets immediate feedback on a wrong number. Use Active Job only for the periodic re-validation of already-stored numbers, where latency does not matter and you want to avoid blocking a web request.
How do I keep my API key out of the Rails codebase?
Use Rails encrypted credentials via bin/rails credentials:edit and read the key with Rails.application.credentials.taxid_api_key, or fall back to an ENV var loaded through dotenv-rails in development. Never commit the key or the master.key file to version control.
What happens when VIES is temporarily unavailable?
The API returns status 'service_unavailable'. In your validator, allow the record to save and flag it for a later re-check rather than rejecting it, so a transient registry outage doesn't block a real business from signing up. Your Sidekiq re-validation job will confirm the number once VIES recovers.
Country-specific API docs:
In-depth integration guide:
EU VAT validation in Ruby / Rails
Validate EU VAT numbers in Ruby or Ruby on Rails using the standard Net::HTTP library. Zero gem dependencies — integrates cleanly with ActiveRecord models and Rails service objects.
Start validating EU VAT numbers in Ruby on Rails
Free plan — 100 validations/month. No credit card required.