Guide24 min readTaxID Team

Real-time Business Registration Number Lookup API Guide 2026

Perform a real-time business registration number lookup with our REST API. This developer guide covers EU VIES, Node.js/Python, and error handling.

business registration number lookupvat validation apivies apistripe eu vatcompany verification

A customer types a business number into your checkout. Your tax logic pauses on that field. If the number is valid, you may need to remove VAT, issue a compliant invoice, and store proof of why you did it. If the lookup fails, you have a mess: the wrong tax treatment, a blocked conversion, or a support ticket that lands on your desk five minutes later.

That's the core problem with business registration number lookup in production. The happy path is easy. The hard part is building something that still behaves sensibly when an upstream registry is slow, a government service returns nonsense, or your billing flow needs an answer before the user closes the tab.

Table of Contents

The Challenge of Global Business Verification

The most annoying version of this problem shows up at checkout. A buyer from another country enters a registration or VAT number and expects your app to instantly know whether tax should apply. Your product team wants a smooth flow. Finance wants auditability. You get stuck in the middle, translating fragile public data into a billing decision.

That sounds manageable until you leave the demo environment.

A man at a desk looking at a computer screen displaying a business registration number checkout page.

Why the lookup surface is fragmented

In the United States, business registration number lookup is anchored in state-level filing systems rather than a single national registry, even though the Census Bureau's Business Register is described as a “current and extensive database” for statistical use in U.S. economic programs and publishes establishment counts, payroll, and employment by county and 6-digit NAICS industry, as explained in the U.S. Census Bureau overview of the Business Register. In practice, that means developers usually end up navigating Secretary of State systems, entity databases, and sometimes SEC filings.

That split matters. It means your code can't assume one query format, one identifier style, or one response shape. It also means support requests often start with, “Why can't your system find this company when I know it exists?”

A clean business registration number lookup flow usually needs more than one search surface. Legal name, entity number, DBA, and jurisdiction all matter. If you want a good primer on the input side of the problem, this guide on how to check tax ID numbers is a useful companion.

Practical rule: Treat business identity as a resolution problem, not a single API call.

Europe adds centralization and a different kind of pain

The EU looks easier from far away because VIES gives you one official path for VAT validation across member states. Then you integrate it. You inherit SOAP, inconsistent operational behavior, and error handling that often feels designed for a human reading a web page, not a backend making tax decisions in milliseconds.

That's why direct government integrations break down in real systems. They're authoritative, but they're brittle. Manual lookups don't scale, and raw upstream calls don't belong in revenue-critical paths without a buffer between your app and the registry.

Here's what usually works better:

  • Use government data as the authority. That's where legitimacy starts.
  • Add a normalization layer. You need one request shape and one response contract.
  • Design for partial failure. Checkout can't collapse because one upstream service is having a bad day.
  • Separate lookup from final business action. Validation informs invoicing. It shouldn't own the whole workflow.

If you're building across multiple markets, the core engineering problem isn't validation alone. It's resilience under inconsistent public infrastructure.

Making Your First Authoritative Lookup via API

The first useful milestone is simple. Send one business number, get back a machine-readable answer, and make sure your app can use it without parsing HTML, SOAP envelopes, or registry-specific fields.

This typically means a POST request from your backend, not from the browser. Keep your API key off the client, log the result, and return only the fields your frontend needs.

Screenshot from https://www.taxid.dev

A minimal request

A practical request payload is tiny. You send the country context and the registration number or VAT number, then let your backend consume a normalized response.

Node.js with axios

const axios = require('axios');

async function lookupBusinessNumber() {
  const response = await axios.post(
    'https://api.taxid.dev/validate',
    {
      countryCode: 'DE',
      taxId: 'DE123456789'
    },
    {
      headers: {
        Authorization: `Bearer ${process.env.TAXID_API_KEY}`,
        'Content-Type': 'application/json'
      }
    }
  );

  return response.data;
}

lookupBusinessNumber()
  .then(console.log)
  .catch(console.error);

Python with requests

import os
import requests

def lookup_business_number():
    response = requests.post(
        "https://api.taxid.dev/validate",
        json={
            "countryCode": "DE",
            "taxId": "DE123456789"
        },
        headers={
            "Authorization": f"Bearer {os.environ['TAXID_API_KEY']}",
            "Content-Type": "application/json"
        },
        timeout=10
    )

    response.raise_for_status()
    return response.json()

print(lookup_business_number())

What a successful response should give you

You want a response contract that maps directly into business logic. Something like this is enough for most billing systems:

{
  "valid": true,
  "companyName": "Example GmbH",
  "companyAddress": "Musterstrasse 1, Berlin",
  "countryCode": "DE",
  "taxId": "DE123456789"
}

Each field should have an immediate use:

Field Why it matters
valid Drives tax treatment and gating logic
companyName Helps match invoice recipient details
companyAddress Useful for invoice records and manual review
countryCode Confirms jurisdiction
taxId Gives you the canonical identifier you validated

What to do with the result immediately

Don't stop at printing JSON in a terminal. Wire the result into a real action.

  • Store the validated identifier: Save the exact tax ID that produced the result.
  • Capture the returned company details: They help finance and support reconcile future disputes.
  • Attach the validation result to the customer record: Billing, invoicing, and renewals should reuse it.
  • Return a reduced frontend payload: The browser often only needs valid, companyName, and a safe status message.

A lookup that never makes it into your billing model is just a demo.

If you need a second reference point for request and response patterns, this walkthrough on VAT number lookup is worth scanning before you start shaping your own DTOs.

A good first implementation fits in one route handler and one service function. The mistake is stopping there and assuming production will be equally polite.

Building Resilient Systems with Robust Error Handling

Most failed integrations don't die on valid numbers. They die on ambiguous failure states. An upstream service times out. A registry is temporarily unavailable. A user pastes junk into the form. Your code treats all of that as one generic exception, and checkout either blocks or inadvertently applies the wrong tax rule.

That's not a validation problem. It's an error classification problem.

An infographic comparing the benefits of robust error handling versus the drawbacks of ignoring errors in systems.

Not every failure means the same thing

You need to separate final failures from temporary failures.

A final failure means the identifier is invalid, malformed, or not usable for the tax treatment you're about to apply. A temporary failure means the answer might exist, but you can't trust the upstream path right now.

Modern registration systems change often enough that freshness itself becomes an engineering concern. Middesk says it has profiles on 100% of registered businesses across the U.S., with 92% of business-registration data records updated within the last 10 days, which is a good reminder that lookup data is active operational data, not a static phone book, as described in Middesk's business registration number lookup discussion.

That has one practical consequence. Your system must assume temporary service disruption is normal.

Handle error codes like business states

A resilient integration maps errors into downstream decisions, making a clean JSON error object worth more than an “invalid input” string buried in an upstream response.

Node.js example

const axios = require('axios');

async function validateTaxId(payload) {
  try {
    const { data } = await axios.post(
      'https://api.taxid.dev/validate',
      payload,
      {
        headers: {
          Authorization: `Bearer ${process.env.TAXID_API_KEY}`
        },
        timeout: 10000
      }
    );

    return {
      state: 'validated',
      data
    };
  } catch (error) {
    const apiError = error.response?.data?.error;

    if (apiError?.code === 'vat_invalid') {
      return {
        state: 'rejected',
        reason: 'The registration number is not valid for tax exemption.'
      };
    }

    if (apiError?.code === 'service_unavailable') {
      return {
        state: 'deferred',
        reason: 'Validation service is temporarily unavailable. Queue for retry or manual review.'
      };
    }

    return {
      state: 'error',
      reason: 'Unexpected validation failure'
    };
  }
}

The key design choice is that rejected and deferred are not the same state.

If the service is down, don't pretend the customer is invalid. Mark the order for review and keep your audit trail clean.

A simple decision table

Error condition System action
Invalid number Deny exemption, ask for correction
Temporary upstream outage Queue retry, allow fallback workflow, or hold for review
Timeout Retry with backoff, don't duplicate charges
Unexpected response shape Log it, fail safely, and avoid automated exemption

What usually goes wrong

Teams often make one of these mistakes:

  • They collapse all errors into HTTP 400: That hides whether the user entered bad data or the upstream service failed.
  • They retry everything: Retrying an obviously invalid number just burns time and noise.
  • They decide tax treatment on the client: Frontend code shouldn't own a compliance decision.
  • They drop the failure context: Support then has no way to explain why the invoice included VAT.

Error handling is where a business registration number lookup turns from a utility into infrastructure. If your billing path depends on public registries, failure semantics are part of the product.

Caching Lookups for Performance and Reliability

A lookup system that hits an external service every time is slower than it needs to be and more fragile than it should be. If the same customer comes back, upgrades a plan, or retries checkout after a card failure, repeating the whole validation path is wasted work.

Caching fixes two problems at once. It reduces latency, and it gives you a buffer when upstream services wobble.

A diagram illustrating a caching workflow for a business lookup system to improve performance and reliability.

Why caching matches real lookup practice

In manual state-level searches, one of the most reliable habits is recording the entity number after the first successful match so later lookups are faster and more accurate. That workflow is described in this guide on California business lookup steps and tips. Application caching is the same idea, just automated.

You already learned the identifier once. Keep it.

Design hint: Cache the validated identifier and the normalized response together. You want both the answer and the evidence that produced it.

A two-layer cache works best

Use two layers, not one.

  1. Provider-side cache
    If your validation provider caches prior authoritative results, repeated lookups return quickly and avoid unnecessary upstream traffic.

  2. Application-side cache
    Your own database or Redis layer lets you keep operating when external services are slow or unavailable.

That second layer matters for billing logic. Your app often doesn't need a fresh lookup on every page load. It needs a consistent answer for known customers, plus a clear policy for when to revalidate.

Practical cache rules

A good cache policy is boring, and that's exactly what you want.

  • Cache by canonical identifier: Normalize casing, spacing, and country prefix first.
  • Store the full normalized response: Don't just save valid: true.
  • Set a revalidation policy tied to business events: New signup, invoice creation, renewal, or changed company details.
  • Keep stale data readable but labeled: Finance may still need the old result during an outage.
  • Don't let the cache bypass audit storage: Cached should not mean undocumented.

Here's a simple flow:

Step Action
Lookup request arrives Normalize identifier
Cache hit Return cached validation result
Cache miss Call external validation service
Valid response Save response and metadata
Upstream failure Use prior known-good result if policy allows, otherwise defer

Caching is one of the few places where performance and compliance align. The same stored identifier that speeds up checkout also reduces repeated manual reconciliation later.

Integrating VAT Validation into Stripe Checkout Flows

Business registration number lookup stops being an abstract backend concern and starts affecting revenue. The buyer enters a VAT number. Your app checks it. If it's valid for the scenario you support, you update the tax treatment before the Stripe session is finalized.

The cleanest implementation splits the work into two phases. First, the frontend collects the VAT number and calls your backend for validation. Second, the backend decides how to create or update the Stripe customer and checkout session.

Frontend trigger and UX behavior

Don't validate on every keypress. Validate when the user leaves the field or explicitly submits the form. That avoids noisy requests and gives the user room to finish typing.

<input id="vatNumber" name="vatNumber" />
<div id="vatStatus"></div>
const vatInput = document.getElementById('vatNumber');
const vatStatus = document.getElementById('vatStatus');

vatInput.addEventListener('blur', async () => {
  const taxId = vatInput.value.trim();

  if (!taxId) return;

  vatStatus.textContent = 'Checking VAT number...';

  const response = await fetch('/api/validate-vat', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ taxId })
  });

  const result = await response.json();

  if (result.valid) {
    vatStatus.textContent = `Validated for ${result.companyName}`;
    hideVatEstimate();
  } else if (result.deferred) {
    vatStatus.textContent = 'We could not confirm this right now. We will review it before final invoicing.';
    showVatEstimate();
  } else {
    vatStatus.textContent = 'Invalid VAT number';
    showVatEstimate();
  }
});

function hideVatEstimate() {
  const el = document.getElementById('vat-line');
  if (el) el.style.display = 'none';
}

function showVatEstimate() {
  const el = document.getElementById('vat-line');
  if (el) el.style.display = 'block';
}

Backend route and Stripe preparation

Your backend should be the only layer that talks to the validation API and Stripe. That keeps secrets contained and makes the tax decision auditable.

const express = require('express');
const axios = require('axios');
const Stripe = require('stripe');

const app = express();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

app.use(express.json());

app.post('/api/validate-vat', async (req, res) => {
  const { taxId } = req.body;

  try {
    const { data } = await axios.post(
      'https://api.taxid.dev/validate',
      { taxId },
      {
        headers: {
          Authorization: `Bearer ${process.env.TAXID_API_KEY}`
        }
      }
    );

    return res.json({
      valid: data.valid,
      companyName: data.companyName,
      companyAddress: data.companyAddress,
      countryCode: data.countryCode,
      taxId: data.taxId
    });
  } catch (error) {
    const code = error.response?.data?.error?.code;

    if (code === 'service_unavailable') {
      return res.json({ valid: false, deferred: true });
    }

    return res.json({ valid: false, deferred: false });
  }
});

app.post('/api/create-checkout-session', async (req, res) => {
  const { email, taxValidation } = req.body;

  const customer = await stripe.customers.create({
    email,
    metadata: {
      tax_id_checked: String(!!taxValidation?.valid),
      tax_id_value: taxValidation?.taxId || ''
    }
  });

  const session = await stripe.checkout.sessions.create({
    mode: 'subscription',
    customer: customer.id,
    line_items: [
      {
        price: process.env.STRIPE_PRICE_ID,
        quantity: 1
      }
    ],
    success_url: `${process.env.APP_URL}/success`,
    cancel_url: `${process.env.APP_URL}/billing`
  });

  res.json({ url: session.url });
});

What belongs in your business logic

A solid implementation usually follows these rules:

  • Frontend collects input: It gives instant feedback but doesn't decide compliance.
  • Backend validates and stores evidence: That's the authoritative record.
  • Stripe customer metadata gets the validated identifier: It helps downstream invoicing and support.
  • Tax display updates before payment: The user sees the expected treatment early.
  • Temporary failures become review states: They do not silently become “invalid.”

If you want a Stripe-specific reference for the surrounding integration pattern, this Stripe VAT integration guide is the right place to compare your flow against a production-oriented setup.

Keep the validation result and the resulting checkout decision in the same audit trail. Otherwise finance will know what happened, but not why.

Compliance Beyond Validation and Final Steps

A valid result is only the beginning. The business reason you care about validation is usually tax treatment, invoice accuracy, and audit defense.

For EU B2B billing, that often means applying reverse charge correctly when the conditions are met. Your system should store the validation result alongside the invoice context that depended on it. If finance gets asked later why VAT wasn't charged, “the form said valid” is not enough. You need the identifier, returned company details, timestamp, and the billing action taken.

Lookup is not the same as legitimacy

This is the part many teams skip. A successful registry lookup doesn't prove the company is safe to onboard, safe to pay, or fully compliant in every other sense.

Guidance on California's bizfile search makes that limitation explicit. A registration search can confirm status, but it does not reveal why a business is suspended, what taxes are owed, ownership details, liens, bankruptcies, or industry-specific licensing, as explained in this article on conducting a Secretary of State business search in California.

That distinction matters outside tax too.

  • Supplier onboarding: Existence is not the same as low risk.
  • Marketplace compliance: Registration doesn't replace licensing checks.
  • Invoicing controls: A valid number doesn't answer whether your records match the legal entity you're paying.

Final operating rules

If you want this system to survive contact with real billing workflows, keep the rules tight:

Rule Why it matters
Validate before tax treatment changes Prevents bad exemptions
Persist the response Supports audits and support cases
Treat outages as operational states Avoids false negatives
Revalidate at meaningful events Keeps stale records from driving invoices
Use lookup as one control, not the whole program Reduces compliance blind spots

A resilient business registration number lookup system doesn't try to make public registries perfect. It assumes they aren't. Then it builds a stable layer around them so checkout stays fast, invoices stay defensible, and your team doesn't spend every outage manually undoing tax mistakes.


If you need a developer-first way to validate VAT and company identification numbers without dealing directly with flaky government integrations, TaxID gives you one REST endpoint, normalized JSON responses, caching, and machine-readable errors that fit cleanly into billing and checkout flows.

AG
Alberto García

Founder, TaxID

Building EU VAT validation tools for developers. Obsessed with compliance automation and developer experience.