npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zyfy-uk/zyfy

v0.1.1

Published

Official Node.js client library for the Zyfy UK data enrichment API

Downloads

204

Readme

zyfy

npm version CI License: MIT

Official Node.js client library for the Zyfy UK data enrichment API.

Two products in one package:

  • Vehicle Intelligence — DVLA + DVSA MOT history, ULEZ, tax status, odometer trend, buy recommendation
  • Postcode Intelligence — broadband, flood risk, crime, property prices, deprivation, air quality, MP data, and more

Get your API key at zyfy.uk/signup. Full API docs at zyfy.uk/docs.

Installation

npm install @zyfy-uk/zyfy

Requires Node.js 20 or later.

Quickstart

import { Zyfy } from '@zyfy-uk/zyfy'

const client = new Zyfy({ apiKey: 'ea_live_...' })

// Vehicle lookup
const vehicle = await client.vehicle.lookup('AB12CDE')
console.log(vehicle.summary?.buyRecommendation)  // "good" | "consider" | "caution" | "avoid"
console.log(vehicle.signals?.motExpiryDate)
console.log(vehicle.quota.remaining)             // requests left this month

// Postcode lookup
const postcode = await client.postcode.lookup('SW1A 2AA')
console.log(postcode.summary?.liveabilityLevel)  // "low" | "medium" | "high"
console.log(postcode.signals?.crime?.rateBand)
console.log(postcode.quota.remaining)

The API key can also be set via the ZYFY_API_KEY environment variable — if it is, you can construct the client with no arguments:

const client = new Zyfy()

Configuration reference

All options are passed to the Zyfy constructor. All are optional if ZYFY_API_KEY is set.

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | ZYFY_API_KEY env var | Your Zyfy API key. | | maxEnrichmentRetries | number | 10 | Auto-retries when enrichmentPending: true. Each retry waits the Retry-After seconds from the response. Set to 0 to disable. | | timeoutMs | number | 10000 | Per-request timeout in milliseconds. Surfaces as NetworkError on expiry. | | baseUrl | string | https://zyfy.uk/v1 | Override for local development or testing. | | debug | boolean | false | Logs requests and responses to stderr. The API key is always redacted. |

Full reference

client.vehicle.lookup(registration, options?)

Look up a single UK vehicle by registration mark. Returns DVLA details, DVSA MOT history, emissions, and computed intelligence.

Full signal reference: zyfy.uk/docs/vehicle.html

| Parameter | Type | Description | |---|---|---| | registration | string | UK registration mark. Spaces and case are normalised automatically. | | options.maxEnrichmentRetries | number | Per-call override of maxEnrichmentRetries. |

Returns: Promise<VehicleResult & { quota: Quota }>


client.vehicle.bulkLookup(registrations, options?)

Synchronous bulk vehicle lookup. Runs sequentially on the server. Up to your tier's bulk cap per call.

Returns: Promise<BulkVehicleResult & { quota: Quota }>

Use isVehicleBulkError(item) to distinguish error items from successful results:

const { results } = await client.vehicle.bulkLookup(['AB12CDE', 'NOTREAL'])
for (const item of results) {
  if (isVehicleBulkError(item)) {
    console.log(item.registration, item.error)  // "not_found" | "invalid_format"
  } else {
    console.log(item.registration, item.summary?.buyRecommendation)
  }
}

client.vehicle.submitBulk(registrations)

Submit an async bulk job. Returns immediately with a jobId. Poll with getJob().

Returns: Promise<BulkJobSubmitted & { quota: Quota }>


client.vehicle.getJob(jobId)

Poll the status of an async bulk job. Check status === "complete" before reading results.

Returns: Promise<BulkJobStatus<BulkVehicleItem> & { quota: Quota }>

status values: "queued" | "processing" | "complete" | "expired"


client.vehicle.deleteJob(jobId)

Delete a bulk job and its results. Jobs expire automatically after 24 hours.

Returns: Promise<{ deleted: string; quota: Quota }>


client.postcode.lookup(postcode, options?)

Look up a single UK postcode. Returns geographic classification, broadband, flood risk, property prices, crime, deprivation, air quality, housing, political, and demographic signals.

Northern Ireland postcodes (BT prefix) are not supported. Throws ValidationError with code: "unsupported_region".

Full signal reference: zyfy.uk/docs/postcode.html

Returns: Promise<PostcodeResult & { quota: Quota }>


client.postcode.nearest(lat, lon, options?)

Find the nearest postcode to a set of WGS84 coordinates. Coordinates must be within the UK bounding box. The response includes a queryPointDistanceMetres field.

| Parameter | Type | Default | Description | |---|---|---|---| | lat | number | — | WGS84 latitude. | | lon | number | — | WGS84 longitude. | | options.radius | number | 1000 | Search radius in metres. Throws NotFoundError if no postcode centroid is found within the radius. | | options.maxEnrichmentRetries | number | — | Per-call override of maxEnrichmentRetries. |

Returns: Promise<PostcodeResult & { quota: Quota }>


client.postcode.bulkLookup(postcodes, options?)

Synchronous bulk postcode lookup.

Returns: Promise<BulkPostcodeResult & { quota: Quota }>

Use isPostcodeBulkError(item) to distinguish errors:

const { results } = await client.postcode.bulkLookup(['SW1A 2AA', 'BT1 1AA'])
for (const item of results) {
  if (isPostcodeBulkError(item)) {
    console.log(item.postcode, item.error)  // "not_found" | "unsupported_region"
  } else {
    console.log(item.postcode, item.summary?.liveabilityLevel)
  }
}

client.postcode.submitBulk(postcodes) / getJob(jobId) / deleteJob(jobId)

Same async bulk pattern as vehicle. See vehicle docs above.


Error handling

All errors extend ZyfyError. Import the specific classes for instanceof checks.

import {
  Zyfy,
  QuotaExhaustedError,
  RateLimitError,
  ValidationError,
  NotFoundError,
  AuthenticationError,
  ApiError,
  NetworkError,
} from '@zyfy-uk/zyfy'

try {
  const result = await client.vehicle.lookup('AB12CDE')
} catch (err) {
  if (err instanceof QuotaExhaustedError) {
    // Monthly quota exhausted. err.resets is an ISO 8601 UTC string indicating
    // when the quota rolls over — null if no monthly reset applies.
    if (err.resets) {
      const resetsAt = new Date(err.resets)
      const hoursUntilReset = Math.round(err.retryAfter / 3600)
      console.error(`Quota exhausted. Resets at ${resetsAt.toUTCString()} (~${hoursUntilReset}h)`)
    } else {
      console.error('Quota exhausted. Contact support to increase your limit.')
    }
  } else if (err instanceof RateLimitError) {
    // Per-minute rate limit exceeded. Back off by err.retryAfter seconds and retry.
    console.warn(`Rate limited. Retrying in ${err.retryAfter}s`)
    await new Promise(r => setTimeout(r, err.retryAfter * 1000))
    // retry the call...
  } else if (err instanceof ValidationError) {
    // Input rejected by the server. err.code identifies the specific problem.
    // Common codes: "unsupported_region" (BT postcodes), "invalid_format"
    console.error(`Validation error: ${err.code}`)
  } else if (err instanceof NotFoundError) {
    console.error('Vehicle or postcode not found')
  } else if (err instanceof AuthenticationError) {
    console.error('Invalid or missing API key')
  } else if (err instanceof ApiError) {
    console.error(`Server error ${err.statusCode}`)
  } else if (err instanceof NetworkError) {
    console.error('Connection failed — check your network')
  }
}

| Error class | HTTP status | When | |---|---|---| | AuthenticationError | 401 | Invalid or missing API key | | NotFoundError | 404 | Vehicle or postcode not found | | ValidationError | 422 | Invalid input; check err.code (e.g. "unsupported_region", "invalid_format") | | RateLimitError | 429 | Per-minute rate limit exceeded; err.retryAfter seconds until safe to retry | | QuotaExhaustedError | 429 | Monthly quota exhausted; err.retryAfter seconds until reset, err.resets ISO 8601 datetime (null if not applicable) | | ApiError | 5xx | Server error; err.statusCode | | NetworkError | — | Connection failure or request timeout |

All error classes expose rawBody: string with the full response body for debugging.

Quota

Every successful response includes a quota object populated from response headers:

const result = await client.vehicle.lookup('AB12CDE')
const { quota } = result

console.log(quota.limit)       // monthly cap — number, or "unlimited"
console.log(quota.used)        // requests consumed this month
console.log(quota.remaining)   // requests left — number, or "unlimited"
console.log(quota.graceLimit)  // buffer above limit before hard block (~10%); null if not applicable
console.log(quota.resets)      // ISO 8601 UTC string of next reset; null if not applicable

remaining reaches zero at the quota limit and further requests are blocked. A small grace buffer (graceLimit) allows a few extra requests above the cap before the hard block kicks in.

resets is null when no monthly cap is in effect. Always null-check before formatting or displaying it.

To avoid hitting the limit unexpectedly in high-volume applications, read quota.remaining after each response and stop or slow down before it reaches zero.

Debug mode

Pass debug: true to log every request and response to stderr:

const client = new Zyfy({ apiKey: '...', debug: true })

Output looks like:

[zyfy] → GET https://zyfy.uk/v1/vehicle/AB12CDE (X-Api-Key: ea_live_***, attempt 1)
[zyfy] ← 200 (quota-remaining: 9958, retry-after: none)

The API key is always redacted — it will never appear in logs regardless of debug mode. Useful for diagnosing unexpected responses, quota consumption, or enrichment retry behaviour in development.

Enrichment retries

Vehicle lookups may return enrichmentPending: true when background enrichment is still running — typically the first time a registration is seen, or when the vehicle's data is being refreshed. When pending, signals, summary, and scores may be null or incomplete. Postcode lookups are always served from a pre-loaded dataset and never set enrichmentPending.

Automatic retries (default)

The client retries automatically up to maxEnrichmentRetries times (default: 10), waiting the number of seconds in the Retry-After response header (typically 5 seconds) between each attempt. The final result is returned once enrichment completes or retries are exhausted — no exception is thrown either way.

Override the retry limit per call:

const result = await client.vehicle.lookup('AB12CDE', { maxEnrichmentRetries: 3 })

Manual retry pattern

If you need control over retry timing — for example, in a queue-based system where you want to reenqueue rather than block the current thread — disable auto-retries and handle enrichmentPending yourself:

const client = new Zyfy({ apiKey: '...', maxEnrichmentRetries: 0 })

let result = await client.vehicle.lookup('AB12CDE')

if (result.enrichmentPending) {
  // Partial data returned — signals/summary/scores may be null.
  // The API will typically have the enriched result ready within 5 seconds.

  // Option A: wait and re-query inline
  await new Promise(r => setTimeout(r, 5_000))
  result = await client.vehicle.lookup('AB12CDE')

  // Option B: in a queue system, persist result.registration and re-process
  // after a delay rather than blocking here.
}

// Use whatever is available — enrichmentPending may still be true
// if enrichment is unusually slow. The data returned is always valid.
if (result.enrichmentPending) {
  console.log(`Partial data for ${result.registration} — enrichment still in progress`)
}
console.log(result.registration, result.summary?.buyRecommendation ?? 'pending')

If auto-retries exhaust while enrichmentPending is still true, the last partial response is returned without throwing. All returned fields are valid — only fields that depend on enrichment may be null.

TypeScript types

All response types are exported from the package. Full definitions:

Quota

Returned on every successful response. Populated from X-Quota-* response headers.

interface Quota {
  limit: number | 'unlimited'
  used: number
  remaining: number | 'unlimited'
  graceLimit: number | null
  resets: string | null           // ISO 8601; null if not applicable
}

VehicleResult

interface VehicleResult {
  registration: string            // uppercase, no spaces
  make: string | null
  model: string | null
  vehicleType: string | null      // "car" | "van" | "motorcycle" | "bus" | "hgv" | "motorhome" | "trailer" | "tractor" | "other"
  colour: string | null
  fuelType: string | null
  engineCapacityCc: number | null
  yearOfManufacture: number | null
  monthOfFirstRegistration: string | null   // YYYY-MM
  vehicleAgeYears: number | null
  summary: VehicleSummary | null
  signals: VehicleSignals | null
  scores: VehicleScores | null
  fleetFailureProfile: FleetFailureProfile | null
  fleetAdvisoryProfile: FleetAdvisoryProfile | null
  sources: VehicleSources
  schemaVersion: string
  enrichmentPending: boolean
  dataAsOf: string                // ISO 8601
  checkedAt: string               // ISO 8601
  quota: Quota
}

interface VehicleSummary {
  buyRecommendation: 'good' | 'consider' | 'caution' | 'avoid' | null
  vehicleRiskLevel: 'low' | 'medium' | 'high' | null
  motRiskLevel: 'low' | 'medium' | 'high' | null
  conditionBand: 'good' | 'fair' | 'poor' | 'bad' | null
  maintenanceBand: 'good' | 'fair' | 'poor' | 'bad' | null
  mileageAnomalyRisk: 'none' | 'low' | 'high' | null
  colourChangeIndicated: boolean | null
  aboveAverageAdvisories: boolean | null
  motFailureDetailAvailable: boolean
}

interface VehicleSignals {
  co2EmissionsGPerKm: number | null
  euroEmissionStandard: string | null
  ulezCompliant: boolean | null
  markedForExport: boolean
  hasOutstandingRecall: boolean | null
  v5cLastIssued: string | null          // YYYY-MM-DD
  taxStatus: string | null
  taxDueDate: string | null             // YYYY-MM-DD
  taxDaysRemaining: number | null
  vedBand: string | null
  vedAnnualCostGbp: number | null
  motStatus: string | null
  motExpiryDate: string | null          // YYYY-MM-DD
  motDaysRemaining: number | null
  imminentMot: boolean
  odometerTrend: 'consistent' | 'increasing' | 'decreasing' | 'erratic' | null
  latestOdometerMiles: number | null
  typicalAnnualMileageMiles: number | null
  odometerVsFleetAverage: 'below_average' | 'average' | 'above_average' | null
  motPassRate: number | null
  totalMotTests: number
  totalMotFailures: number
  totalAdvisoryCount: number
  totalFailureItemCount: number
  latestAdvisoryCount: number
  latestFailureItemCount: number
  dangerousDefectEver: boolean
  highFailureHistory: boolean
  advisoryTrend: 'increasing' | 'stable' | 'decreasing' | null
  advisoryMomentum: 'worsening' | 'stable' | 'improving' | null
  daysSinceLastFailure: number | null
  failuresLast24Months: number | null
  advisoriesLast3Tests: number | null
  trendWindowTests: number
  firstMotDate: string | null           // YYYY-MM-DD
  lastMotDate: string | null            // YYYY-MM-DD
  lastMotResult: string | null
  firstMotDue: string | null            // YYYY-MM-DD
  failureClusters: string[] | null
  repeatFailureCount: number | null
  advisoryClusters: string[] | null
  ncapSafetyRating: NcapSafetyRating | null
  drivetrainStressProfile: DrivetrainStressProfile | null
}

interface NcapSafetyRating {
  overallStars: number | null         // 0–5
  adultOccupant: number | null        // 0–100
  childOccupant: number | null        // 0–100
  vulnerableRoadUsers: number | null  // 0–100
  safetyAssist: number | null         // 0–100
  testedYear: number | null
}

interface DrivetrainStressProfile {
  likelyDrivingPattern: 'short_urban' | 'mixed' | 'long_distance' | null
  dpfRisk: 'low' | 'elevated' | 'high' | null   // diesel only
}

interface VehicleScores {
  motRiskScore: number | null           // 0–1, lower is better
  conditionScore: number | null         // 0–1, higher is better
  conditionPercentile: number | null
  maintenanceScore: number | null       // 0–1, higher is better
  maintenancePercentile: number | null
  failureRateRatio: number | null       // 1.0 = fleet average
  advisoryRateRatio: number | null      // 1.0 = fleet average
  benchmarkSampleSize: number | null
  avgAdvisoriesPerTestForMMY: number | null
  avgFailuresPerTestForMMY: number | null
  offRoadLikelihoodScore: number | null // 0–1, higher = more likely off-road
  scoreConvention: string
}

interface FleetFailureProfile {
  mileageBand: string
  sampleSize: number
  topFailures: FleetItem[]
}

interface FleetAdvisoryProfile {
  mileageBand: string
  sampleSize: number
  topAdvisories: FleetItem[]
}

interface FleetItem {
  category: string
  rate: number
}

interface VehicleSources {
  motHistory: string
  mutableData: string
  safetyRating: string | null
}

PostcodeResult

interface PostcodeResult {
  postcode: string                // formatted with space: "SW1A 2AA"
  outwardCode: string
  inwardCode: string | null       // null for outward-code-only queries
  latitude: number | null
  longitude: number | null
  eastings: number | null
  northings: number | null
  country: string | null
  region: string | null
  adminDistrict: string | null
  adminCounty: string | null
  adminWard: string | null
  parish: string | null
  parliamentaryConstituency: string | null
  nhsTrust: string | null
  lsoa: string | null
  msoa: string | null
  ruralUrbanClassification: string | null
  summary: PostcodeSummary | null
  signals: PostcodeSignals | null
  scores: PostcodeScores | null
  percentiles: PostcodePercentiles | null   // Starter+ tiers only
  geographyCodes: GeographyCodes
  queryPointDistanceMetres: number | null   // nearest endpoint only
  sources: PostcodeSources
  schemaVersion: string
  dataAsOf: string                // ISO 8601
  checkedAt: string               // ISO 8601
  quota: Quota
}

interface PostcodeSummary {
  propertyRiskLevel: 'low' | 'medium' | 'high' | null
  liveabilityLevel: 'low' | 'medium' | 'high' | null
  insuranceRiskLevel: 'low' | 'medium' | 'high' | null
  investmentOutlook: 'weak' | 'fair' | 'good' | 'strong' | null
  growthSignal: 'strong_positive' | 'positive' | 'neutral' | 'weak_negative' | 'strong_negative' | null
  dataConfidence: 'low' | 'medium' | 'high'
  areaTrajectory: 'improving' | 'stable' | 'declining' | 'mixed' | null
  familySuitability: 'poor' | 'fair' | 'good' | 'excellent' | null
  retirementSuitability: 'poor' | 'fair' | 'good' | 'excellent' | null
}

interface PostcodeSignals {
  broadband: PostcodeBroadbandSignals | null
  flood: PostcodeFloodSignals | null
  property: PostcodePropertySignals | null
  crime: PostcodeCrimeSignals | null
  environment: PostcodeEnvironmentSignals | null
  housing: PostcodeHousingSignals | null
  political: PostcodePoliticalSignals | null
  deprivation: PostcodeDeprivationSignals | null
  demographics: PostcodeDemographicsSignals | null
}

interface PostcodeBroadbandSignals {
  superfast: boolean | null
  ultrafast: boolean | null
  gigabit: boolean | null
}

interface PostcodeFloodSignals {
  riversSea: 'high' | 'medium' | 'low' | 'very_low' | null
  groundwater: 'high' | 'medium' | 'low' | 'very_low' | null
  riversSeaTrend: 'worsening' | 'stable' | 'improving' | 'insufficient_data' | null
  groundwaterTrend: 'worsening' | 'stable' | 'improving' | 'insufficient_data' | null
}

interface PostcodePropertySignals {
  averagePrice: number | null             // median price, last 12 months
  priceLow: number | null                 // 25th percentile
  priceHigh: number | null                // 75th percentile
  priceTrend: number | null               // YoY % change, e.g. 5.2 = +5.2%
  priceTrendPeriodMonths: number
  transactionVolume: number | null        // residential transactions, last 12 months
  granularity: 'postcode' | 'sector' | 'district' | null
  trendConfidence: 'high' | 'medium' | 'low' | null
}

interface PostcodeCrimeSignals {
  rateBand: 'very_low' | 'low' | 'medium' | 'high' | 'very_high' | null
  dataGranularity: PostcodeCrimeGranularity | null
  categories: PostcodeCrimeCategories | null
}

interface PostcodeCrimeGranularity {
  band: string | null       // "lsoa" (England/Wales) | "datazone" (Scotland)
  categories: string | null // "lsoa" (England/Wales) | "local_authority" (Scotland)
}

interface PostcodeCrimeCategories {
  violence: PostcodeCrimeCategory | null
  property: PostcodeCrimeCategory | null
  vehicle: PostcodeCrimeCategory | null
  antisocial: PostcodeCrimeCategory | null
  drugs: PostcodeCrimeCategory | null
  damage: PostcodeCrimeCategory | null
}

interface PostcodeCrimeCategory {
  band: 'very_low' | 'low' | 'medium' | 'high' | 'very_high' | null
  trend: 'increasing' | 'stable' | 'decreasing' | 'insufficient_data' | null
}

interface PostcodeEnvironmentSignals {
  airQualityBand: 'very_low' | 'low' | 'moderate' | 'high' | null
  airQualityTrend: 'worsening' | 'stable' | 'improving' | 'insufficient_data' | null
  no2UgM3: number | null            // annual mean NO2 µg/m³
  pm25UgM3: number | null           // annual mean PM2.5 µg/m³
  radonPotential: 'very_low' | 'low' | 'medium' | 'high' | 'very_high' | null
  greenSpaceProximityMetres: number | null
  isNationalPark: boolean | null
  isAonb: boolean | null
  isGreenBelt: boolean | null
}

interface PostcodeHousingSignals {
  epcAverageRating: string | null           // A–G
  councilTaxBand: CouncilTaxBandEstimate | null
  dominantPropertyType: 'detached' | 'semi_detached' | 'terraced' | 'flat' | 'other' | null
}

interface CouncilTaxBandEstimate {
  lower: string   // A–I
  upper: string   // A–I; equals lower when a single band is determined
  source: 'exact_nrs' | 'derived_hmlr' | 'derived_lsoa'
}

interface PostcodePoliticalSignals {
  mpName: string | null
  mpParty: string | null
  mpPartyColour: string | null
}

interface PostcodeDeprivationSignals {
  imdDecile: number | null    // 1 = most deprived, 10 = least deprived; England only
  imdTrend: 'improving' | 'stable' | 'declining' | 'insufficient_data' | null
}

interface PostcodeDemographicsSignals {
  percOwnerOccupied: number | null
  percPrivateRented: number | null
  percNoCarVan: number | null
  medianAge: number | null
  percEconomicallyActive: number | null
}

interface PostcodeScores {
  propertyRiskScore: number | null    // 0–1, lower is better
  liveabilityScore: number | null     // 0–1, higher is better
  investmentScore: number | null      // 0–1, higher is better
  affordabilityIndex: number | null   // 0–1, higher is better
  scoreConvention: string
}

interface PostcodePercentiles {
  floodRivers: number | null
  floodGroundwater: number | null
  crimeRate: number | null
  imd: number | null
  propertyPrice: number | null
  propertyPriceRegional: number | null
  radon: number | null
  airQuality: number | null
  greenSpaceProximity: number | null
  epc: number | null
}

interface GeographyCodes {
  adminDistrict: string | null
  adminCounty: string | null
  adminWard: string | null
  parliamentaryConstituency: string | null
  lsoa: string | null
  msoa: string | null
}

interface PostcodeSources {
  geography: string
  flood: string
  crime: string
  property: string
  deprivation: string
  broadband: string
  environment: string
  epc: string
  greenSpace: string
  demographics: string | null
}

Bulk types

interface BulkVehicleResult {
  total: number
  results: BulkVehicleItem[]
  quota: Quota
}

interface BulkPostcodeResult {
  total: number
  results: BulkPostcodeItem[]
  quota: Quota
}

// BulkVehicleItem is VehicleResult | VehicleBulkItemError
interface VehicleBulkItemError {
  registration: string
  error: 'not_found' | 'invalid_format'
}

// BulkPostcodeItem is PostcodeResult | PostcodeBulkItemError
interface PostcodeBulkItemError {
  postcode: string
  error: 'not_found' | 'unsupported_region'
}

interface BulkJobSubmitted {
  jobId: string
  status: 'queued'
  total: number
  pollUrl: string
  quota: Quota
}

interface BulkJobStatus<T> {
  jobId: string
  status: 'queued' | 'processing' | 'complete' | 'expired'
  total: number
  done: number
  createdAt: string
  completedAt: string | null
  expiresAt: string
  results: T[] | null
  quota: Quota
}

Examples

Runnable examples are in the examples/ directory.

# Set your key first
export ZYFY_API_KEY=ea_live_...

# Single lookups
VEHICLE_REG=AB12CDE npm run example:vehicle
POSTCODE="SW1A 2AA" npm run example:postcode

# Geographic queries
LAT=51.508 LON=-0.1281 npm run example:nearest
LAT=51.508 LON=-0.1281 RADIUS=500 npm run example:within

# Bulk lookups
VEHICLE_REGS=AB12CDE,XY34FGH npm run example:bulk-vehicle
POSTCODES="SW1A 2AA,M1 1AE" npm run example:bulk-postcode

# Error handling scenarios
npx tsx examples/errors.ts vehicle-invalid
npx tsx examples/errors.ts postcode-not-found
npx tsx examples/errors.ts postcode-ni
npx tsx examples/errors.ts bad-auth

Versioning

This library follows SemVer. See CHANGELOG.md for version history.

0.x.x releases may include breaking changes between minor versions. Stability guaranteed from 1.0.0.


zyfy.uk · Docs · Sign up · GitHub