Skip to content

zyfy-uk/zyfy-node

Repository files navigation

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

About

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

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors