Skip to main content

Python SDK for the Visor Public API

Project description

visor-python

CI

visor-python is an unofficial community Python SDK for the Visor Public API — a vehicle inventory search platform covering new, used, and certified pre-owned listings from dealers across the US. It provides a thin, fully-typed wrapper around the REST API with sync and async clients, Pydantic response models, and auto-pagination helpers.

Disclaimer: This is an unofficial community SDK and is not affiliated with or endorsed by Visor (Currents Systems Inc.).

Pre-1.0 notice: This package is in initial development (0.x). Minor version bumps may include breaking changes. Pin to a specific minor version in production and review the CHANGELOG before upgrading.

Install

pip install visor-python

Requires Python 3.10+ and no non-standard runtime dependencies beyond httpx and pydantic.

Quick start

from visor import VisorClient, ListingsFilter, iter_listings

with VisorClient() as client:  # reads VISOR_API_KEY from env
    # Search for used Toyota Tacomas in Texas under $40k
    page = client.filter_listings(
        ListingsFilter(
            make=["Toyota"],
            model=["Tacoma"],
            inventory_type=["used"],
            state=["TX"],
            max_price=40_000,
        )
    )
    for listing in page.data:
        price = f"${listing.price:,}" if listing.price is not None else "N/A"
        print(f"{listing.year} {listing.make} {listing.model}{price}")

    # Look up a specific VIN
    vin = client.lookup_vin("4T1DAACKXTU765422", include=["price_history"])
    msrp = f"${vin.build.combined_msrp:,}" if vin.build.combined_msrp is not None else "N/A"
    print(msrp)

Geo filtering

Pass a postal_code and radius (miles) to search near a location:

page = client.filter_listings(
    ListingsFilter(
        make=["Honda"],
        model=["CR-V"],
        postal_code="90210",
        radius=50,
    )
)

radius requires exactly one of postal_code or latitude/longitude. Passing radius alone (or with neither) raises ValueError before any network call.

Paginating all results

iter_listings (sync) and paginate_listings (async) iterate every page automatically:

from visor import VisorClient, ListingsFilter, iter_listings

with VisorClient() as client:
    for listing in iter_listings(
        client,
        ListingsFilter(make=["Ford"], state=["TX"]),
    ):
        print(listing.vin, listing.price)

For dealers, use iter_dealers / paginate_dealers in the same way.

client.filter_listings(...) returns a single page (ListingsPage). Use the iter_* / paginate_* helpers when you need all results.

Async

import asyncio
from visor import AsyncVisorClient, ListingsFilter, paginate_listings

async def main() -> None:
    async with AsyncVisorClient() as client:
        # Single page
        page = await client.filter_listings(
            ListingsFilter(make=["Toyota"], state=["TX"], max_price=40_000)
        )
        for listing in page.data:
            price = f"${listing.price:,}" if listing.price is not None else "N/A"
            print(listing.vin, price)

        # All pages
        async for listing in paginate_listings(
            client,
            ListingsFilter(make=["Toyota"], state=["TX"]),
        ):
            print(listing.vin)

asyncio.run(main())

Configuration

API key

Pass your key explicitly or export VISOR_API_KEY before running:

client = VisorClient(api_key="vsr_live_...")
# or
# export VISOR_API_KEY=vsr_live_...
client = VisorClient()

You need your own Visor API key — see api.visor.vin for details. Use of the API is governed by Visor's API terms; you are responsible for complying with them.

Timeout

Default request timeout is 30 seconds. Override at construction time:

client = VisorClient(timeout=10.0)

Base URL (advanced)

base_url defaults to the production API. Override it for local testing or staging:

client = VisorClient(base_url="http://localhost:8080")

Key concepts

ListingsFilter is shared

ListingsFilter is accepted by both filter_listings() and dealer_inventory(). Build one filter object and reuse it across both methods.

fields is response projection, not filtering

ListingsFilter.fields controls which fields the API returns — it does not filter which listings match. Example:

filter = ListingsFilter(
    make=["Toyota"],
    fields=["vin", "price", "miles"],
)

id and vin are always returned by the API regardless of the fields projection.

Responses are Pydantic models

All responses — ListingsPage, ListingDetail, VinDetail, etc. — are Pydantic v2 models. Access fields as attributes and use standard Pydantic methods (.model_dump(), .model_json_schema(), etc.) as needed.

Error handling

All methods raise typed exceptions from visor.exceptions. The SDK does not retry automatically — RateLimitError.retry_after gives you the hint to build your own retry logic.

import time
from visor import VisorClient, ListingsFilter, RateLimitError, VisorAPIError

def fetch_with_backoff(client: VisorClient, f: ListingsFilter) -> object:
    for attempt in range(4):
        try:
            return client.filter_listings(f)
        except RateLimitError as e:
            wait = e.retry_after if e.retry_after is not None else 2 ** attempt
            print(f"Rate limited — waiting {wait}s")
            time.sleep(wait)
        except VisorAPIError as e:
            raise  # surface non-rate-limit errors immediately
    raise RuntimeError("Exhausted retries")

Exception hierarchy:

Exception When
VisorAPIError Base for all API errors; has .status_code
AuthError 401 — invalid or missing API key
NotFoundError 404 — resource does not exist
RateLimitError 429 — includes .retry_after (seconds, or None)

Debugging

Inspect the exceptionVisorAPIError carries .status_code and a message from the API.

Check retry_after — for RateLimitError, .retry_after is the number of seconds to wait (or None if the API did not provide a value).

Request-level logging — visor-python uses httpx internally. Enable httpx logging to see raw requests and responses:

import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("httpx").setLevel(logging.DEBUG)

Community

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

visor_python-0.1.0.tar.gz (20.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

visor_python-0.1.0-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

Details for the file visor_python-0.1.0.tar.gz.

File metadata

  • Download URL: visor_python-0.1.0.tar.gz
  • Upload date:
  • Size: 20.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for visor_python-0.1.0.tar.gz
Algorithm Hash digest
SHA256 1a90a926662fd269b78ba07a43a30a40e1aadac23dd72118948c246fc5f879f2
MD5 35f57955138f4f72ae35ed7161fca028
BLAKE2b-256 abf900b2a114a33b9e84250e337c1674424cc8090475b53507ee55b914b052c9

See more details on using hashes here.

Provenance

The following attestation bundles were made for visor_python-0.1.0.tar.gz:

Publisher: publish.yml on whitewalls86/visor-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file visor_python-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: visor_python-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 20.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for visor_python-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 23f49e50768ccb19f5e37e7efa3c6c97c14c0058f38abc22d4f78547b7344136
MD5 c4eb1a6c85d2d4ae1f6712a8e28694d8
BLAKE2b-256 b49aca18ac1a054434b185790649040444bbaffdd0c30d395addba89a4f3ab12

See more details on using hashes here.

Provenance

The following attestation bundles were made for visor_python-0.1.0-py3-none-any.whl:

Publisher: publish.yml on whitewalls86/visor-python

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page