JSPM

@verifyvat/sdk

0.1.1
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 1
  • Score
    100M100P100Q25567F
  • License MIT

Official TypeScript SDK for VerifyVAT

Package Exports

  • @verifyvat/sdk
  • @verifyvat/sdk/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@verifyvat/sdk) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

VerifyVAT TypeScript SDK

This is the official TypeScript SDK for the VerifyVAT API. It provides a safe, convenient, and consistent way to call the API and to reason about results.

By using this SDK, you get:

  1. Safe API calls:
    Authentication, retries with backoff, envelope validation, and a single error type with useful context for debugging.
  2. Structured results:
    Rich, typed results that include what sources said, coverage levels, issues, and more, so you can understand the full context of a verification.
  3. Reasoning helpers:
    Opinionated helpers to turn complex verification results into simple decision signals, as well as pickers to select the best name, address, or identifier for an entity based on your needs.

If you haven't already, start with VerifyVAT's documentation to understand the API's concepts and features, then come back here for SDK-specific usage and examples.


Installation

pnpm install @verifyvat/sdk

Node requirement: ≥ 18 (uses built-in fetch).


Quickstart

import { createVerifyVatClient, Verifier } from '@verifyvat/sdk'

const client = createVerifyVatClient()
const verifier = new Verifier(client)

const verification = await verifier.verifyId({ id: '09446231', country: 'GB' })
console.log(verification)

Environment variables are read automatically if set:

VERIFYVAT_API_KEY=your_api_key_here
# Optional for custom deployments:
# VERIFYVAT_BASE_URL=https://api.verifyvat.com/v1

FP helpers vs OO wrappers

The SDK exports both FP helpers (functions) and OO wrappers (classes) for core features. Both call the same underlying client and return the same results, the difference is in ergonomics and style.

FP helpers

When you want a simple, direct way to call the API without needing to manage instances of classes, the FP helpers are a great choice. They are straightforward functions that take the client as the first argument, followed by the parameters for the specific operation.

import { verifyId, inferIdType } from '@verifyvat/sdk'

const v = await verifyId(client, { id: '914778271', type: 'no_orgnr' })
const t = await inferIdType(client, { id: '914778271', country: 'NO' })

OO wrappers

If you prefer an object-oriented style, or if you want to group related operations together, the OO wrappers provide a convenient way to do that. They are classes that you instantiate with the client once, and then call methods on the instance for specific operations.

import { Verifier, TypeInferrer } from '@verifyvat/sdk'

const verifier = new Verifier(client, { suppressErrors: true })
const v = await verifier.verifyId({ id: '914778271', type: 'no_orgnr' })

const inferrer = new TypeInferrer(client, { suppressErrors: true })
const t = await inferrer.inferIdType({ id: '914778271', country: 'NO' })

The client

The client is created via createVerifyVatClient(...). It exists to make the boring but critical parts consistent.

The VerifyVatClient handles authentication via API key and standard headers, validation of the API envelope, timeouts, network failures, and retries with backoff, including rate-limit handling. It throws a single error type VerifyVatError with useful context for debugging.

We recommend creating the client once and reusing it across your app. It's designed to be lightweight and efficient.

import { createVerifyVatClient, VerifyVatLogEvent } from '@verifyvat/sdk'

// While all options are optional, `apiKey` is required if not set as an env var.
const client = createVerifyVatClient({
    // Explicitly pass you api key and base URL, or set them as env vars
    apiKey: process.env.VERIFYVAT_API_KEY!,
    // baseUrl: 'https://api.verifyvat.com/v1',
    // How long to wait for a response before giving up
    timeoutMs: 10_000,
    // If you want to use a custom fetch implementation (e.g. in a non-Node environment)
    // fetchImpl: customFetch,
    // Custom logger for retry attempts, errors, etc.
    logger: (event: VerifyVatLogEvent) => { ... },
    // Retry policy configuration
    retryPolicy: {
        maxRetries: 3, // max number of retries before giving up
        backoffBaseMs: 1_000, // initial backoff delay
        backoffMaxMs: 30_000, // max backoff delay
    }
})

If you need to cancel a request or attach headers, pass request options at call time:

import { verifyId } from '@verifyvat/sdk'

const controller = new AbortController()

const v = await verifyId(
    client,
    { id: '09446231', country: 'GB' },
    { request: { signal: controller.signal, headers: { 'x-correlation-id': 'abc' } } },
)

// To cancel the request:
controller.abort()

To learn more about the common response format from VerifyVAT, see Response envelope.


Verify an ID

Verification answers: "what did sources return, and how complete/reliable was it?"

import { Verifier } from '@verifyvat/sdk'

const verifier = new Verifier(client)
const verification = await verifier.verifyId({ id: '09446231', country: 'GB' })
console.log(verification)

You typically use verifyId in two modes:

1) Plain mode (throws on request failure)

This is the standard way to call the API. If the request fails (network error, non-2xx status, or an "unsuccessful" envelope), it throws a VerifyVatError with details about what went wrong.

const v = await verifier.verifyId({ id: '09446231', country: 'GB' })

2) Pipeline mode (suppressErrors: true)

This is what you want in bulk/batch work, where "error as data" is better than throwing. With suppressErrors: true, the helper returns a tagged fallback verification object even when the request fails.

const v = await verifier.verifyId({ id: '09446231', country: 'GB' }, { suppressErrors: true })

For more on error handling strategies and request failures, see Error handling.


Bulk verification (concurrency + per-item outputs)

Use verifyManyIds when you have a small batch of inputs to verify at once.

import { Verifier } from '@verifyvat/sdk'

const verifier = new Verifier(client)
const results = await verifier.verifyManyIds(
    [
        { id: '09446231', country: 'GB' },
        { id: '914778271', type: 'no_orgnr' },
        '5493000IBP32UQZ0KL24',
    ],
    // These are the default settings for bulk operations
    { concurrency: 5, suppressErrors: true },
)

for (const r of results) {
    console.log(r.input, r.verification)
}

This gives you:

  • the normalized input the SDK actually processed (e.g. with inferred type)
  • the verification result for each input, including in-band errors when suppressErrors is true

verifyManyIds supports both array and object/record inputs. With object inputs, the output is an object with the same keys, which can be more convenient for lookups.

const results = await verifier.verifyManyIds(
    {
        a: { id: '09446231', country: 'GB' },
        b: { id: '914778271', type: 'no_orgnr' },
        c: '5493000IBP32UQZ0KL24',
    },
    { concurrency: 5, suppressErrors: true },
)

console.log(results.a.input, results.a.verification)

For more on bulk verification, see Bulk verification.


describeVerification: turn results into signals

A verification result is a rich object with many fields about what sources said, coverage, issues, etc. The describeVerification helper turns this into a consistent set of decision signals you can use in your product logic, without having to write custom rules for every source and edge case.

import { Verifier } from '@verifyvat/sdk'

const verifier = new Verifier(client)
const verification = await verifier.verifyId({ id: '09446231', country: 'GB' })
const d = verifier.describeVerification(verification)

if (d.retryRecommended) {
    // Re-add to the verification queue for a retry later
} else if (d.reviewRecommended) {
    // Flag as edge case for manual review
} else if (d.isConfirmed) {
    // Proceed with confidence
} else {
    // Reject or block
}

Policy overrides

There might be cases where you want to override the default classification logic based on your product's needs or risk appetite. For example, you might want to never trust cache data, or to flag certain syntax issues for review.

You can do this by passing a policy function to describeVerification that tweaks the decision signals based on the verification context.

const d2 = verifier.describeVerification(verification, {
    policy: (ctx) => {
        // We decide to never accept data from cache, even if it's "fresh"
        if (ctx.origin === 'fresh-cache') {
            return { isConfirmed: false, retryRecommended: true }
        }
        // We decide to review any identifiers with invalid syntax
        if (ctx.issues.includes('syntax-invalid')) {
            return { reviewRecommended: true }
        }
    },
})

Per-source overrides

You can override per-source classification if you intend to reason about specific sources in your product logic. For example, you could decide to accept stale cache data only if it comes from a specific source.

const d3 = verifier.describeVerification(verification, {
    sourcePolicy: ({ source }) => {
        if (source.id === 'xx-reg') {
            return { hasStaleOutcome: false }
        }
    },
})

const anyStale = d3.sources.some((s) => s.hasStaleOutcome)

For more practical examples of how to use describeVerification and how to think about decision signals, see Verify & decide.
If you want to understand what stale and fresh data means and how to reason about it, see Caching and stale data.


Infer ID type

Use this when a user pastes "something" and you don't yet know what type of identifier it is (VAT number, company number, LEI, etc.). The SDK returns ranked candidates with confidence scores, and a helper to pick the best one when confidence is high.

To learn more about how inference works and how to use it in your product, see Infer ID type.

import { inferIdType, pickBestInferredIdType } from '@verifyvat/sdk'

const result = await inferIdType({ id: '914778271', region: 'EMEA' })
const inferredType = pickBestInferredIdType(result, {
    confidence: { min: 0.8 },
})

if (!inferredType) {
    // ask for country or specific ID type
} else {
    console.log(
        `Most likely type: ${inferredType.type} in ${inferredType.details.country}, with confidence ${inferredType.confidence}`,
    )
}

OO wrapper version:

import { TypeInferrer } from '@verifyvat/sdk'

const inferrer = new TypeInferrer(client)
const result = await inferrer.inferIdType({ id: '914778271', region: 'EMEA' })

DomainGraph: ID types, sources, coverage

DomainGraph is a read-only model built from the API's ID type and data source definitions. You can use it to understand what types and sources are available, what coverage they have, and to make decisions like "can we verify this ID without calling the API?" or "should we show this source's data on the UI?".

See List supported IDs and List data sources for more on the underlying API endpoints.

Fetch once, use everywhere

You can instantiate a DomainGraph and reuse it across your app. It has built-in caching and refresh logic, so you don't have to worry about staleness. You can then call methods like getTypes, getSources, getCountries, etc. to reason about the data.

import { DomainGraph } from '@verifyvat/sdk'

const graph = new DomainGraph({ client })

// Get all supported ID types (with optional filters)
const types = await graph.getTypes({
    // All filters are optional and can be combined as needed
    country: ['BE', 'DE', 'GB'], // only ID types from these countries
    region: ['EU', 'EMEA'], // only ID types from these regions
    validation: ['registry', 'syntactic'], // only ID types with these validation modes
    coverage: 'full', // only ID types with full coverage
    source: 'eu-vies', // only ID types backed by this source
})

for (const t of types) {
    console.log(`${t.id} (${t.name}): ${t.validation} validation, ${t.sources.length} sources`)
}

// Get all integrated data sources (with optional filters)
const sources = await graph.getSources({
    id: ['be-bce', 'eu-vies', 'no-brreg'], // only these specific sources
    country: ['BE', 'NL', 'NO'], // only sources covering these countries
    region: 'EU', // only sources from this region
    idType: ['be_vat', 'nl_vat', 'no_vat'], // only sources that back these ID types
    coverage: ['full', 'partial'], // only sources with this coverage level
    active: true, // only integrations that are currently active on the API
})

for (const s of sources) {
    console.log(
        `${s.id} (${s.name}): covers ${s.jurisdictions.join(', ')} with ${s.types.length} ID types`,
    )
}

// Get all countries for which the API has coverage (with optional filters)
const countries = await graph.getCountries({
    from: ['types', 'sources'], // gather country data from these datasets (only "types", only "sources", or both)
    region: 'EU', // only countries from this region
    coverage: 'full', // only countries with at least one ID type with full coverage
    validation: 'registry', // only countries with at least one ID type with registry validation
})

console.log(`Currently supports ${countries.length} countries: ${countries.join(', ')}`)

// Get a structured view per-country, combining types, sources, and coverage info (with optional filters)
const views = await graph.getCountryViews(
    {
        country: ['IT', 'NO'], // only build views for these countries
        region: 'EU', // only build views for countries in this region
        coverage: 'partial', // only consider types with at least partial coverage
        validation: ['registry', 'syntactic'], // only consider types with these validation modes
    },
    {
        shape: 'record', // return a record keyed by country code, or an array of views (default)
    },
)

for (const country in views) {
    const view = views[country]
    console.log(
        `${view.country}: ${view.types.length} types, ${view.sources.length} sources, ${view.full.length} with full coverage`,
    )
}

// Get coverage level for an ID type in a specific country (with optional filters)
const coverage = await graph.getCoverage({
    country: 'IT', // country code to check coverage for (required)
    type: 'it_vat', // ID type code to check coverage for (required)
    region: 'EU', // optional region constraint
    validation: 'registry', // optional validation mode constraint
})

switch (coverage) {
    case 'none':
        console.log('No coverage: cannot verify this ID type in this country')
        break
    case 'partial':
        console.log(
            'Partial coverage: can verify some IDs of this type in this country, but not all',
        )
        break
    case 'full':
        console.log('Full coverage: can verify all IDs of this type in this country')
        break
}

Offline usage and deployment snapshot

The DomainGraph is built to be used online with the API, but you can also export its data and use it offline if needed. A typical use case for this is to "bake" a snapshot of the domain graph into a deployment, and refresh it periodically (e.g. daily or weekly) via a background job.

import { DomainGraph } from '@verifyvat/sdk'

const onlineGraph = new DomainGraph({ client })
// Extract the raw data object that DomainGraph is built on
const snapshot = await onlineGraph.data()

// Save this snapshot to a file, a database, or where makes sense for you:
// saveSnapshot(snapshot)

// Later, you can load this snapshot and create a DomainGraph instance from it.
// This instance will never access the network, but will still give you access to all the helper methods like getTypes, getSources, etc.
const offlineGraph = DomainGraph.fromData(snapshot)

For more details on how and when to use this features, see Domain graph.


Entities: selecting the right name/address/identifier

When verification returns an entity, you usually need to display only one name, one address, or one identifier, but the entity contains many, possibly across time.

This is where the SDK's entity pickers come in: they help you select the best entry based on a set of opinionated rules, without having to write custom logic for every edge case.

import { Entity } from '@verifyvat/sdk'

if (!verification.entity) throw new Error('No entity data returned')

const entity = new Entity(verification.entity)
const name = entity.pickBestName()
const address = entity.pickBestAddress()

console.log(name?.value, address?.value)

The selection DSL

All pickers use the same underlying DSL to express rules, which is based on five main concepts:

  1. filter: which records are eligible
  2. prefer: what should be prioritised
  3. avoid: what should be penalised
  4. rank: tie-breaking rules (especially time-based)
  5. strategy: escape hatch for custom rules that can't be expressed with the above

This is consistent across:

  • names (getNames, findName, pickBestName, pickBestNames)
  • addresses (getAddresses, findAddress, pickBestAddress, pickBestAddresses)
  • identifiers (getIdentifiers, findIdentifier, pickBestIdentifier, pickBestIdentifiers)
  • registrations (getRegistrations, findRegistration, pickBestRegistration, pickBestRegistrations)
  • status records (getStatusRecords, findStatusRecord, pickBestStatusRecord, pickBestStatusRecords)
  • timeline events (getTimelineEvents, findTimelineEvent, pickBestTimelineEvent, pickBestTimelineEvents)

getXs and findX methods support filter only, and return all matching records (the former returns an array, the latter a single record or null). pickBestXs and pickBestX methods support all five concepts and return the best matching records (the former returns an array, the latter a single record or null).

Example 1: pick a display name for a UI card

import { Entity } from '@verifyvat/sdk'

const entity = new Entity(verification.entity)

const displayName = entity.pickBestName({
    prefer: {
        // Prefer names with an English language tag
        language: 'en',
        // Prefer legal names, then short names, then trading names
        kind: ['legal', 'short', 'trading'],
    },
    avoid: {
        // Avoid names that are empty or very short
        value: (name) => !name || name.length < 2,
    },
    rank: {
        // As a tiebreaker, prefer the most recent name that is still valid today (i.e. not expired)
        until: ['null', 'latest'],
    },
})

Example 2: pick the address, but avoid mailing ones

import { Entity } from '@verifyvat/sdk'

const entity = new Entity(verification.entity)

const addr = entity.pickBestAddress({
    avoid: { kind: 'mailing' },
})

Example 3: pick the best identifier for a VAT scheme

import { Entity } from '@verifyvat/sdk'

const entity = new Entity(verification.entity)

const id = entity.pickBestIdentifier({
    prefer: {
        // We look for ID types with VAT/GST/TIN hints in their code
        id: (v) => !!v && /_(vat|gst|tin)$/i.test(v),
    },
    rank: {
        // We rank higher identifiers linked to registrations that are currently valid, or if not possible, that expired last
        registrations: {
            until: ['null', 'latest'],
        },
    },
})

Time travel: "what was true on a date?"

Many entity records have validity windows (since / until). The SDK exposes a derived timeframe field, and allows you to filter and rank based on time in a intuitive way.

Pick a name valid on a specific day

This snippet will consider only names that were valid on May 1st, 2021, and among those will pick the best one based on the other rules (not shown here for simplicity).

const nameOnDate = pickBestName(entity, {
    on: '2021-05-01',
})

Pick the address valid during a period

This snippet will consider only addresses that were valid at some point during 2021, and among those will pick the best one based on the other rules (not shown here for simplicity).

const addrInPeriod = pickBestAddress(entity, {
    between: ['2021-01-01', '2021-12-31'],
    // or alternatively:
    onOrAfter: '2021-01-01',
    onOrBefore: '2021-12-31',
})

Change the date comparison logic

By using the pickers' time-related advanced options, you can:

  • change the date comparison granularity (day, month, year)
  • match against a specific timezone
  • specify custom date input formats

This snippet will consider only names that were valid in May 2021 in the Europe/London timezone, and among those will prefer legal names over others.

const nameOnDateInTimezone = pickBestName(entity, {
    filter: {
        on: '05/01/2021',
    },
    prefer: {
        kind: 'legal',
    },
    config: {
        timeZone: 'Europe/London',
        dateGranularity: 'month',
        dateInputFormat: 'MM/DD/YYYY',
    },
})

Date comparison utilities

If you need to build a custom time-based rule that can't be easily expressed with the pickers' options, you can use the SDK's public time utilities to compare validity windows against specific dates or periods.

In particular, you can use:

  • parseCalendarLiteral and toCalendarLabel to parse and format dates in the SDK's calendar label format (e.g. "2021-05" for May 2021)
  • compareLabels and compareDates as standard comparators that understand the SDK's calendar label semantics (e.g. "2021-05" is before "2021-06", and "2021" is before "2021-05")
  • isOnDate, isBeforeDate, isOnOrBeforeDate, isAfterDate, isOnOrAfterDate to check if a validity window is active on, before, or after a specific date
  • isWithinRange and isBetweenRange to check if a validity window overlaps with a specific period

If you want to know more about how date are handled by our API, see Data normalisation.


Error handling

Most of the time you only need two strategies:

Strategy A:
throw and catch VerifyVatError at the edges of your system (e.g. API route handlers, background job processors, etc.) to map to your error handling and retry logic.

import { VerifyVatError } from '@verifyvat/sdk'

try {
    const v = await verifyId(client, { id: '09446231', country: 'GB' })
} catch (err) {
    if (err instanceof VerifyVatError) {
        // map to your API error shape
    }
    throw err
}

Strategy B: suppress errors into the response object when you want to treat them as data (e.g. in bulk operations), and check for them in-band when processing results.

const results = await verifyManyIds(client, inputs, {
    suppressErrors: true,
})

// results include in-band failures; your loop never crashes

To learn more about it, see Error handling and Bulk verification.


License

MIT License

Copyright (c) 2025 - 2026 RS1 Project

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.