Official Python client library for the Zyfy UK data enrichment API
Project description
zyfy
Official Python 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
pip install zyfy
Requires Python 3.9 or later.
Quickstart
Synchronous
from zyfy import Zyfy
client = Zyfy(api_key="ea_live_...")
# Vehicle lookup
vehicle = client.vehicle.lookup("AB12CDE")
print(vehicle.summary.buy_recommendation) # "good" | "consider" | "caution" | "avoid"
print(vehicle.signals.mot_expiry_date)
print(vehicle.quota.remaining) # requests left this month
# Postcode lookup
postcode = client.postcode.lookup("SW1A 2AA")
print(postcode.summary.liveability_level) # "low" | "medium" | "high"
print(postcode.signals.crime.rate_band)
print(postcode.quota.remaining)
Asynchronous
import asyncio
from zyfy import AsyncZyfy
async def main() -> None:
async with AsyncZyfy(api_key="ea_live_...") as client:
vehicle = await client.vehicle.lookup("AB12CDE")
print(vehicle.summary.buy_recommendation)
postcode = await client.postcode.lookup("SW1A 2AA")
print(postcode.signals.crime.rate_band)
asyncio.run(main())
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:
client = Zyfy() # reads ZYFY_API_KEY
client = AsyncZyfy() # same
Both clients support context managers for automatic cleanup:
with Zyfy() as client:
result = client.vehicle.lookup("AB12CDE")
async with AsyncZyfy() as client:
result = await client.vehicle.lookup("AB12CDE")
Configuration reference
All options are keyword-only. All are optional if ZYFY_API_KEY is set.
| Option | Type | Default | Description |
|---|---|---|---|
api_key |
str |
ZYFY_API_KEY env var |
Your Zyfy API key. |
max_enrichment_retries |
int |
10 |
Auto-retries when enrichment_pending=True. Each retry waits the Retry-After seconds from the response. Set to 0 to disable. |
timeout_ms |
int |
10000 |
Per-request timeout in milliseconds. Surfaces as NetworkError on expiry. |
base_url |
str |
https://zyfy.uk/v1 |
Override for local development or testing. |
debug |
bool |
False |
Logs requests and responses via the zyfy logger at DEBUG level. The API key is always redacted. |
Full reference
client.vehicle.lookup(registration, *, max_enrichment_retries=None)
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 |
str |
UK registration mark. Spaces and case are normalised automatically. |
max_enrichment_retries |
int | None |
Per-call override of max_enrichment_retries. |
Returns: VehicleResult
client.vehicle.bulk_lookup(registrations, *, max_enrichment_retries=None)
Synchronous bulk vehicle lookup. Runs sequentially on the server. Up to your tier's bulk cap per call.
Returns: BulkVehicleResult
Use is_vehicle_bulk_error(item) to distinguish error items from successful results:
from zyfy import Zyfy, is_vehicle_bulk_error
result = client.vehicle.bulk_lookup(["AB12CDE", "NOTREAL"])
for item in result.results:
if is_vehicle_bulk_error(item):
print(item.registration, item.error) # "not_found" | "invalid_format"
else:
print(item.registration, item.summary.buy_recommendation if item.summary else None)
client.vehicle.submit_bulk(registrations)
Submit an async bulk job. Returns immediately with a job_id. Poll with get_job().
Returns: BulkJobSubmitted
client.vehicle.get_job(job_id)
Poll the status of an async bulk job. Check status == "complete" before reading results.
Returns: BulkJobStatus[VehicleResult | VehicleBulkItemError]
status values: "queued" | "processing" | "complete" | "expired"
client.vehicle.delete_job(job_id)
Delete a bulk job and its results. Jobs expire automatically after 24 hours.
Returns: DeletedJob
client.postcode.lookup(postcode, *, max_enrichment_retries=None)
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. Raises ValidationError with code="unsupported_region".
Full signal reference: zyfy.uk/docs/postcode.html
Returns: PostcodeResult
client.postcode.nearest(lat, lon, *, radius=None, max_enrichment_retries=None)
Find the nearest postcode to a set of WGS84 coordinates. Coordinates must be within the UK bounding box. The response includes a query_point_distance_metres field.
| Parameter | Type | Default | Description |
|---|---|---|---|
lat |
float |
— | WGS84 latitude. |
lon |
float |
— | WGS84 longitude. |
radius |
int | None |
1000 |
Search radius in metres. Raises NotFoundError if no postcode centroid is found within the radius. |
max_enrichment_retries |
int | None |
— | Per-call override of max_enrichment_retries. |
Returns: PostcodeResult
client.postcode.bulk_lookup(postcodes, *, max_enrichment_retries=None)
Synchronous bulk postcode lookup.
Returns: BulkPostcodeResult
Use is_postcode_bulk_error(item) to distinguish errors:
from zyfy import Zyfy, is_postcode_bulk_error
result = client.postcode.bulk_lookup(["SW1A 2AA", "BT1 1AA"])
for item in result.results:
if is_postcode_bulk_error(item):
print(item.postcode, item.error) # "not_found" | "unsupported_region"
else:
print(item.postcode, item.summary.liveability_level if item.summary else None)
client.postcode.submit_bulk(postcodes) / get_job(job_id) / delete_job(job_id)
Same async bulk pattern as vehicle. submit_bulk returns BulkJobSubmitted, get_job returns BulkJobStatus[PostcodeResult | PostcodeBulkItemError], delete_job returns DeletedJob.
Error handling
All errors extend ZyfyError. Import the specific classes for isinstance checks.
import time
from zyfy import (
Zyfy,
QuotaExhaustedError,
RateLimitError,
ValidationError,
NotFoundError,
AuthenticationError,
ApiError,
NetworkError,
)
try:
result = client.vehicle.lookup("AB12CDE")
except QuotaExhaustedError as exc:
# Monthly quota exhausted. exc.resets is an ISO 8601 UTC string indicating
# when the quota rolls over — None if no monthly reset applies.
if exc.resets:
hours_until_reset = round(exc.retry_after / 3600)
print(f"Quota exhausted. Resets at {exc.resets} (~{hours_until_reset}h)")
else:
print("Quota exhausted. Contact support to increase your limit.")
except RateLimitError as exc:
# Per-minute rate limit exceeded. Back off by exc.retry_after seconds and retry.
print(f"Rate limited. Retrying in {exc.retry_after}s")
time.sleep(exc.retry_after)
# retry the call...
except ValidationError as exc:
# Input rejected by the server. exc.code identifies the specific problem.
# Common codes: "unsupported_region" (BT postcodes), "invalid_format"
print(f"Validation error: {exc.code}")
except NotFoundError:
print("Vehicle or postcode not found")
except AuthenticationError:
print("Invalid or missing API key")
except ApiError as exc:
print(f"Server error {exc.status_code}")
except NetworkError as exc:
print(f"Connection failed: {exc.cause}")
| Error class | HTTP status | When |
|---|---|---|
AuthenticationError |
401 | Invalid or missing API key |
NotFoundError |
404 | Vehicle or postcode not found |
ValidationError |
422 | Invalid input; check exc.code (e.g. "unsupported_region", "invalid_format") |
RateLimitError |
429 | Per-minute rate limit exceeded; exc.retry_after seconds until safe to retry |
QuotaExhaustedError |
429 | Monthly quota exhausted; exc.retry_after seconds until reset, exc.resets ISO 8601 datetime (None if not applicable) |
ApiError |
5xx | Server error; exc.status_code |
NetworkError |
— | Connection failure or request timeout |
All error classes expose raw_body: str with the full response body for debugging.
Quota
Every successful response includes a quota object populated from response headers:
result = client.vehicle.lookup("AB12CDE")
quota = result.quota
print(quota.limit) # monthly cap — int, or "unlimited"
print(quota.used) # requests consumed this month
print(quota.remaining) # requests left — int, or "unlimited"
print(quota.grace_limit) # buffer above limit before hard block (~10%); None if not applicable
print(quota.resets) # ISO 8601 UTC string of next reset; None for unlimited plans
remaining reaches zero at the quota limit and further requests are blocked. A small grace buffer (grace_limit) allows a few extra requests above the cap before the hard block kicks in.
resets is None when no monthly cap is in effect. Always check for None before formatting or displaying it.
To avoid hitting the limit unexpectedly in high-volume applications, read quota.remaining after each response and slow down before it reaches zero.
Debug mode
Pass debug=True to log every request and response via the zyfy logger at DEBUG level:
import logging
logging.basicConfig(level=logging.DEBUG)
client = Zyfy(api_key="...", 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 enrichment_pending=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 None or incomplete. Postcode lookups are always served from a pre-loaded dataset and never set enrichment_pending.
Automatic retries (default)
The client retries automatically up to max_enrichment_retries 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 raised either way.
Override the retry limit per call:
result = client.vehicle.lookup("AB12CDE", max_enrichment_retries=3)
Manual retry pattern
If you need control over retry timing — for example, in a task queue where you want to reenqueue rather than block the worker — disable auto-retries and handle enrichment_pending yourself:
import time
client = Zyfy(api_key="...", max_enrichment_retries=0)
result = client.vehicle.lookup("AB12CDE")
if result.enrichment_pending:
# Partial data returned — signals/summary/scores may be None.
# The API will typically have the enriched result ready within 5 seconds.
# Option A: wait and re-query inline
time.sleep(5)
result = client.vehicle.lookup("AB12CDE")
# Option B: in a task queue, store result.registration and resubmit
# the task after a delay rather than blocking here.
# Use whatever is available — enrichment_pending may still be True
# if enrichment is unusually slow. The data returned is always valid.
if result.enrichment_pending:
print(f"Partial data for {result.registration} — enrichment still in progress")
recommendation = result.summary.buy_recommendation if result.summary else "pending"
print(result.registration, recommendation)
If auto-retries exhaust while enrichment_pending is still True, the last partial response is returned without raising. All returned fields are valid — only fields that depend on enrichment may be None.
Python type reference
All response types are frozen dataclasses and exported from the package.
Quota
Returned on every successful response. Populated from X-Quota-* response headers.
@dataclass(frozen=True)
class Quota:
limit: int | str # int, or "unlimited"
used: int
remaining: int | str # int, or "unlimited"
grace_limit: int | None
resets: str | None # ISO 8601; None for unlimited plans
VehicleResult
@dataclass(frozen=True)
class VehicleResult:
registration: str # uppercase, no spaces
make: str | None
model: str | None
vehicle_type: str | None # "car" | "van" | "motorcycle" | "bus" | "hgv" | "motorhome" | "trailer" | "tractor" | "other"
colour: str | None
fuel_type: str | None
engine_capacity_cc: int | None
year_of_manufacture: int | None
month_of_first_registration: str | None # YYYY-MM
vehicle_age_years: float | None
summary: VehicleSummary | None
signals: VehicleSignals | None
scores: VehicleScores | None
fleet_failure_profile: FleetFailureProfile | None
fleet_advisory_profile: FleetAdvisoryProfile | None
sources: VehicleSources
schema_version: str
enrichment_pending: bool
data_as_of: str # ISO 8601
checked_at: str # ISO 8601
quota: Quota | None # None when returned as a bulk item
@dataclass(frozen=True)
class VehicleSummary:
buy_recommendation: str | None # "good" | "consider" | "caution" | "avoid"
vehicle_risk_level: str | None # "low" | "medium" | "high"
mot_risk_level: str | None # "low" | "medium" | "high"
condition_band: str | None # "good" | "fair" | "poor" | "bad"
maintenance_band: str | None # "good" | "fair" | "poor" | "bad"
mileage_anomaly_risk: str | None # "none" | "low" | "high"
colour_change_indicated: bool | None
above_average_advisories: bool | None
mot_failure_detail_available: bool
@dataclass(frozen=True)
class VehicleSignals:
co2_emissions_g_per_km: float | None
euro_emission_standard: str | None
ulez_compliant: bool | None
marked_for_export: bool
has_outstanding_recall: bool | None
v5c_last_issued: str | None # YYYY-MM-DD
tax_status: str | None
tax_due_date: str | None # YYYY-MM-DD
tax_days_remaining: int | None
ved_band: str | None
ved_annual_cost_gbp: float | None
mot_status: str | None
mot_expiry_date: str | None # YYYY-MM-DD
mot_days_remaining: int | None
imminent_mot: bool
odometer_trend: str | None # "consistent" | "increasing" | "decreasing" | "erratic"
latest_odometer_miles: int | None
typical_annual_mileage_miles: int | None
odometer_vs_fleet_average: str | None # "below_average" | "average" | "above_average"
mot_pass_rate: float | None
total_mot_tests: int
total_mot_failures: int
total_advisory_count: int
total_failure_item_count: int
latest_advisory_count: int
latest_failure_item_count: int
dangerous_defect_ever: bool
high_failure_history: bool
advisory_trend: str | None # "increasing" | "stable" | "decreasing"
advisory_momentum: str | None # "worsening" | "stable" | "improving"
days_since_last_failure: int | None
failures_last_24_months: int | None
advisories_last_3_tests: int | None
trend_window_tests: int
first_mot_date: str | None # YYYY-MM-DD
last_mot_date: str | None # YYYY-MM-DD
last_mot_result: str | None
first_mot_due: str | None # YYYY-MM-DD
failure_clusters: list[str] | None
repeat_failure_count: int | None
advisory_clusters: list[str] | None
ncap_safety_rating: NcapSafetyRating | None
drivetrain_stress_profile: DrivetrainStressProfile | None
@dataclass(frozen=True)
class NcapSafetyRating:
overall_stars: int | None # 0–5
adult_occupant: float | None # 0–100
child_occupant: float | None # 0–100
vulnerable_road_users: float | None # 0–100
safety_assist: float | None # 0–100
tested_year: int | None
@dataclass(frozen=True)
class DrivetrainStressProfile:
likely_driving_pattern: str | None # "short_urban" | "mixed" | "long_distance"
dpf_risk: str | None # "low" | "elevated" | "high" (diesel only)
@dataclass(frozen=True)
class VehicleScores:
mot_risk_score: float | None # 0–1, lower is better
condition_score: float | None # 0–1, higher is better
condition_percentile: float | None
maintenance_score: float | None # 0–1, higher is better
maintenance_percentile: float | None
failure_rate_ratio: float | None # 1.0 = fleet average
advisory_rate_ratio: float | None # 1.0 = fleet average
benchmark_sample_size: int | None
avg_advisories_per_test_for_mmy: float | None
avg_failures_per_test_for_mmy: float | None
off_road_likelihood_score: float | None # 0–1, higher = more likely off-road
score_convention: str
@dataclass(frozen=True)
class FleetFailureProfile:
mileage_band: str
sample_size: int
top_failures: list[FleetItem]
@dataclass(frozen=True)
class FleetAdvisoryProfile:
mileage_band: str
sample_size: int
top_advisories: list[FleetItem]
@dataclass(frozen=True)
class FleetItem:
category: str
rate: float
@dataclass(frozen=True)
class VehicleSources:
mot_history: str
mutable_data: str
safety_rating: str | None
PostcodeResult
@dataclass(frozen=True)
class PostcodeResult:
postcode: str # formatted with space: "SW1A 2AA"
outward_code: str
inward_code: str | None # None for outward-code-only queries
latitude: float | None
longitude: float | None
eastings: int | None
northings: int | None
country: str | None
region: str | None
admin_district: str | None
admin_county: str | None
admin_ward: str | None
parish: str | None
parliamentary_constituency: str | None
nhs_trust: str | None
lsoa: str | None
msoa: str | None
rural_urban_classification: str | None
summary: PostcodeSummary | None
signals: PostcodeSignals | None
scores: PostcodeScores | None
percentiles: PostcodePercentiles | None # Starter+ tiers only
geography_codes: GeographyCodes
query_point_distance_metres: float | None # nearest endpoint only
sources: PostcodeSources
schema_version: str
data_as_of: str # ISO 8601
checked_at: str # ISO 8601
quota: Quota | None # None when returned as a bulk item
@dataclass(frozen=True)
class PostcodeSummary:
property_risk_level: str | None # "low" | "medium" | "high"
liveability_level: str | None # "low" | "medium" | "high"
insurance_risk_level: str | None # "low" | "medium" | "high"
investment_outlook: str | None # "weak" | "fair" | "good" | "strong"
growth_signal: str | None # "strong_positive" | "positive" | "neutral" | "weak_negative" | "strong_negative"
data_confidence: str # "low" | "medium" | "high"
area_trajectory: str | None # "improving" | "stable" | "declining" | "mixed"
family_suitability: str | None # "poor" | "fair" | "good" | "excellent"
retirement_suitability: str | None
@dataclass(frozen=True)
class PostcodeSignals:
broadband: PostcodeBroadbandSignals | None
flood: PostcodeFloodSignals | None
property: PostcodePropertySignals | None
crime: PostcodeCrimeSignals | None
environment: PostcodeEnvironmentSignals | None
housing: PostcodeHousingSignals | None
political: PostcodePoliticalSignals | None
deprivation: PostcodeDeprivationSignals | None
demographics: PostcodeDemographicsSignals | None
@dataclass(frozen=True)
class PostcodeBroadbandSignals:
superfast: bool | None
ultrafast: bool | None
gigabit: bool | None
@dataclass(frozen=True)
class PostcodeFloodSignals:
rivers_sea: str | None # "high" | "medium" | "low" | "very_low"
groundwater: str | None # "high" | "medium" | "low" | "very_low"
rivers_sea_trend: str | None # "worsening" | "stable" | "improving" | "insufficient_data"
groundwater_trend: str | None
@dataclass(frozen=True)
class PostcodePropertySignals:
average_price: float | None # median price, last 12 months
price_low: float | None # 25th percentile
price_high: float | None # 75th percentile
price_trend: float | None # YoY % change, e.g. 5.2 = +5.2%
price_trend_period_months: int
transaction_volume: int | None # residential transactions, last 12 months
granularity: str | None # "postcode" | "sector" | "district"
trend_confidence: str | None # "high" | "medium" | "low"
@dataclass(frozen=True)
class PostcodeCrimeSignals:
rate_band: str | None # "very_low" | "low" | "medium" | "high" | "very_high"
data_granularity: PostcodeCrimeGranularity | None
categories: PostcodeCrimeCategories | None
@dataclass(frozen=True)
class PostcodeCrimeGranularity:
band: str | None # "lsoa" | "datazone"
categories: str | None # "lsoa" | "local_authority"
@dataclass(frozen=True)
class PostcodeCrimeCategories:
violence: PostcodeCrimeCategory | None
property: PostcodeCrimeCategory | None
vehicle: PostcodeCrimeCategory | None
antisocial: PostcodeCrimeCategory | None
drugs: PostcodeCrimeCategory | None
damage: PostcodeCrimeCategory | None
@dataclass(frozen=True)
class PostcodeCrimeCategory:
band: str | None # "very_low" | "low" | "medium" | "high" | "very_high"
trend: str | None # "increasing" | "stable" | "decreasing" | "insufficient_data"
@dataclass(frozen=True)
class PostcodeEnvironmentSignals:
air_quality_band: str | None # "very_low" | "low" | "moderate" | "high"
air_quality_trend: str | None
no2_ug_m3: float | None # annual mean NO2 µg/m³
pm25_ug_m3: float | None # annual mean PM2.5 µg/m³
radon_potential: str | None # "very_low" | "low" | "medium" | "high" | "very_high"
green_space_proximity_metres: float | None
is_national_park: bool | None
is_aonb: bool | None
is_green_belt: bool | None
@dataclass(frozen=True)
class PostcodeHousingSignals:
epc_average_rating: str | None # A–G
council_tax_band: CouncilTaxBandEstimate | None
dominant_property_type: str | None # "detached" | "semi_detached" | "terraced" | "flat" | "other"
@dataclass(frozen=True)
class CouncilTaxBandEstimate:
lower: str # A–I
upper: str # A–I; equals lower when a single band is determined
source: str # "exact_nrs" | "derived_hmlr" | "derived_lsoa"
@dataclass(frozen=True)
class PostcodePoliticalSignals:
mp_name: str | None
mp_party: str | None
mp_party_colour: str | None
@dataclass(frozen=True)
class PostcodeDeprivationSignals:
imd_decile: int | None # 1 = most deprived, 10 = least deprived; England only
imd_trend: str | None # "improving" | "stable" | "declining" | "insufficient_data"
@dataclass(frozen=True)
class PostcodeDemographicsSignals:
perc_owner_occupied: float | None
perc_private_rented: float | None
perc_no_car_van: float | None
median_age: float | None
perc_economically_active: float | None
@dataclass(frozen=True)
class PostcodeScores:
property_risk_score: float | None # 0–1, lower is better
liveability_score: float | None # 0–1, higher is better
investment_score: float | None # 0–1, higher is better
affordability_index: float | None # 0–1, higher is better
score_convention: str
@dataclass(frozen=True)
class PostcodePercentiles:
flood_rivers: float | None
flood_groundwater: float | None
crime_rate: float | None
imd: float | None
property_price: float | None
property_price_regional: float | None
radon: float | None
air_quality: float | None
green_space_proximity: float | None
epc: float | None
@dataclass(frozen=True)
class GeographyCodes:
admin_district: str | None
admin_county: str | None
admin_ward: str | None
parliamentary_constituency: str | None
lsoa: str | None
msoa: str | None
@dataclass(frozen=True)
class PostcodeSources:
geography: str
flood: str
crime: str
property: str
deprivation: str
broadband: str
environment: str
epc: str
green_space: str
demographics: str | None
Bulk types
@dataclass(frozen=True)
class BulkVehicleResult:
total: int
results: list[VehicleResult | VehicleBulkItemError]
quota: Quota
@dataclass(frozen=True)
class BulkPostcodeResult:
total: int
results: list[PostcodeResult | PostcodeBulkItemError]
quota: Quota
@dataclass(frozen=True)
class VehicleBulkItemError:
registration: str
error: str # "not_found" | "invalid_format"
@dataclass(frozen=True)
class PostcodeBulkItemError:
postcode: str
error: str # "not_found" | "unsupported_region" | "invalid_format"
@dataclass(frozen=True)
class BulkJobSubmitted:
job_id: str
status: str # "queued"
total: int
poll_url: str
quota: Quota
@dataclass(frozen=True)
class BulkJobStatus(Generic[T]):
job_id: str
status: str # "queued" | "processing" | "complete" | "expired"
total: int
done: int
created_at: str # ISO 8601
completed_at: str | None # ISO 8601; None until complete
expires_at: str # ISO 8601
results: list[T] | None # None until status == "complete"
quota: Quota
@dataclass(frozen=True)
class DeletedJob:
deleted: str # job_id that was deleted
quota: Quota
Type guards
def is_vehicle_bulk_error(
item: VehicleResult | VehicleBulkItemError,
) -> TypeGuard[VehicleBulkItemError]: ...
def is_postcode_bulk_error(
item: PostcodeResult | PostcodeBulkItemError,
) -> TypeGuard[PostcodeBulkItemError]: ...
Examples
Runnable examples are in the examples/ directory.
# Set your key first
export ZYFY_API_KEY=ea_live_...
# Single lookups
VEHICLE_REG=AB12CDE python3 examples/vehicle.py
POSTCODE="SW1A 2AA" python3 examples/postcode.py
# Geographic queries
LAT=51.508 LON=-0.1281 python3 examples/nearest.py
LAT=51.508 LON=-0.1281 RADIUS=500 python3 examples/within.py
# Bulk lookups
VEHICLE_REGS=AB12CDE,XY34FGH python3 examples/bulk_vehicle.py
POSTCODES="SW1A 2AA,M1 1AE" python3 examples/bulk_postcode.py
# Error handling scenarios
python3 examples/errors.py vehicle-invalid
python3 examples/errors.py postcode-not-found
python3 examples/errors.py postcode-ni
python3 examples/errors.py 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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zyfy-0.1.1.tar.gz.
File metadata
- Download URL: zyfy-0.1.1.tar.gz
- Upload date:
- Size: 44.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
209019d2f05352423e359f3749fb2b26fceafc078569777384e6ebaec6cf5e54
|
|
| MD5 |
ffdb72d90cad045ab7016df5cdd20788
|
|
| BLAKE2b-256 |
70aa604c407dda0a16c10965b3a48702ead2a46218d334e60d2f65a8c51c9d49
|
File details
Details for the file zyfy-0.1.1-py3-none-any.whl.
File metadata
- Download URL: zyfy-0.1.1-py3-none-any.whl
- Upload date:
- Size: 33.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
44d7ab79af4213c07935a1c64f8379eea91c78023de74c0d186a61eaf9bae645
|
|
| MD5 |
cf39450044eb238ded9cd29ac769943c
|
|
| BLAKE2b-256 |
122522198b224aa60c60f00c66086c7467d74cb0149b21f274c307e9b706cac8
|