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.1.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.1-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: visor_python-0.1.1.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.1.tar.gz
Algorithm Hash digest
SHA256 e5d101d745576d1194ba6c4cb2046054ad6dd05b3b3a384b5ed590d8071d7e35
MD5 74c5c17f9e0e06f03244eb7f2959f348
BLAKE2b-256 34be89a0a42ec37d9b75cd9828cc349ca322fe2312e8bc1cbd335dc35e2beb1e

See more details on using hashes here.

Provenance

The following attestation bundles were made for visor_python-0.1.1.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.1-py3-none-any.whl.

File metadata

  • Download URL: visor_python-0.1.1-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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 399a1e2bfb7c4c60427189282c06aa2ce7075314f70dff924bb159f3e916186e
MD5 20e496e8d69ed2ea93387a94d7b84e59
BLAKE2b-256 ac85d7a53735ab638fd470f46f20cc1d9c7ac75af7c42b4d6d355d033913178f

See more details on using hashes here.

Provenance

The following attestation bundles were made for visor_python-0.1.1-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