Skip to main content
Errors use a stable JSON envelope:
{
  "error": {
    "type": "validation_error",
    "code": "unknown_query_parameter",
    "message": "Unknown query parameter: bad_filter."
  },
  "_tag": "PublicApiValidationError"
}
Use error.code for programmatic handling. Use error.message for logs and operator-facing diagnostics.

Status codes

StatusTypeRetry?Meaning
400validation_error or client_errorNoThe request shape is invalid, a query parameter is unknown, a value is unsupported, or a CLI compatibility request must be updated.
401authentication_errorNoThe API key is missing or invalid.
402billing_errorNoBilling state, balance, or monthly spend cap blocks the request.
403permission_errorNoThe API key is valid but lacks the required scope.
404not_found_errorUsually noA VIN, listing, or dealer detail record was not found.
429rate_limit_errorYesWait for Retry-After when present, then retry.
503platform_errorYesRetry with bounded exponential backoff.

Rate limits

Rate limits are enforced at the API account level. All keys on the same account share the same limits. Successful responses include rate-limit headers:
X-RateLimit-Tier: tier_1
X-RateLimit-Limit-10s: 20
X-RateLimit-Remaining-10s: 19
X-RateLimit-Limit-60s: 60
X-RateLimit-Remaining-60s: 59
Rate-limited responses use HTTP 429 and include Retry-After when available:
Retry-After: 10

Retry pattern

Keep retries bounded. Visor endpoints are read-only, but repeated successful requests can still be billable.
async function visorFetch(path, { maxAttempts = 3 } = {}) {
  const url = `https://api.visor.vin/v1${path}`;

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const response = await fetch(url, {
      headers: {
        Authorization: `Bearer ${process.env.VISOR_API_KEY}`
      }
    });

    if (response.ok) return response.json();

    const retryAfter = Number(response.headers.get("retry-after") || 0);
    const retryable = response.status === 429 || response.status === 503;
    if (!retryable || attempt === maxAttempts) {
      throw new Error(await response.text());
    }

    const waitMs = retryAfter > 0
      ? retryAfter * 1000
      : Math.min(1000 * 2 ** (attempt - 1), 8000);

    await new Promise((resolve) => setTimeout(resolve, waitMs));
  }
}

Billing and failed requests

Prices are per successful request, not per returned row. Successful zero-result searches are billable. Validation, authentication, permission, billing, rate-limit, and platform errors are not successful paid responses. Detail 404 not_found_error responses are not billable.

Common validation fixes

Error patternFix
unknown_query_parameterRemove the unsupported parameter or check the API reference for the endpoint.
Unsupported inventory_type, keywords, or dealer_typeUse the documented vocabulary or discover values with /v1/facets.
limit above 100Page with limit=100 and offset.
facet_value_limit above 100Use facet_value_limit=100 and narrow with filters.
sort=distance without geographyAdd postal_code or latitude and longitude.

CLI compatibility errors

Most CLI releases follow the public API contract. Old-but-compatible releases receive advisory update signals. If a CLI release is known to send requests the platform cannot safely serve, the API returns 400 client_error with code: "unsupported_cli_version" and targeted update instructions in update_url and update_command.