Skip to main content

Snapvisor Python SDK

The official Python SDK and CLI for Snapvisor, the visual regression testing platform. Upload a directory of screenshots as a build from any Python test suite or CI pipeline — and reach every other operation the platform publishes — with no Node.js required.

Snapvisor is a fork of Argos. This SDK speaks the same upload protocol as @snapvisor/core / @argos-ci/core, so it is a drop-in way to send builds from Python, and it still reads the ARGOS_* environment variables the JavaScript SDKs use so it works unchanged in pipelines already wired for Argos/Snapvisor.

Install

pip install snapvisor

Requires Python 3.10+. Runtime dependencies: httpx and attrs.

Quick start

import snapvisor

result = snapvisor.upload("screenshots/", token="<project-token>")
print(result.build_url)
export SNAPVISOR_TOKEN="<project-token>"   # ARGOS_TOKEN also works
snapvisor upload ./screenshots
# Build created: https://app.snapvisor.io/<account>/<project>/builds/<n>

Two entry points

You want to… Use
Upload screenshots as a build snapvisor.upload() / snapvisor upload
Read or write anything else (builds, projects, comments, reviews, changes, deployments, analytics) snapvisor.Snapvisor

Both share one transport policy: retries with exponential backoff, IETF draft-8 rate-limit handling on 429, request-id correlation, and a typed error hierarchy.

Uploading builds

from snapvisor import upload, BuildOptions

result = upload(
    "screenshots",
    token="...",  # or SNAPVISOR_TOKEN / ARGOS_TOKEN
    build_name="my-suite",  # optional, for multi-build setups
    branch="main",  # optional; resolved from env, CI, then git
    commit="abc...def",  # optional; resolved from env, CI, then git
    reference_branch="main",  # optional baseline
    threshold=0.1,  # optional default diff threshold
    concurrency=8,  # files uploaded in parallel
    build=BuildOptions(
        pr_number=77,
        mode="ci",
        subset=True,
        metadata={"testReport": {"status": "passed"}},
    ),
)

print(result.build_url, result.build_id, result.build_number)

upload() returns an UploadResult. aupload() is the identical async version.

Screenshot metadata and Playwright traces

Put sidecars next to a screenshot and the SDK picks them up — the same convention the JavaScript test-runner integrations write:

screenshots/
  home.png
  home.png.argos.json      # viewport, url, colorScheme, test info, transient.threshold
  home.png.pw-trace.zip    # uploaded as the screenshot's Playwright trace
{
  "url": "https://example.com/",
  "viewport": { "width": 1280, "height": 720 },
  "colorScheme": "dark",
  "test": { "title": "home page", "titlePath": ["suite", "home page"] },
  "transient": { "threshold": 0.25, "baseName": "base.png", "parentName": "parent.png" }
}

CI detection

Running inside GitHub Actions, GitLab CI, CircleCI, Buildkite, Travis, Azure Pipelines, or Jenkins, the SDK fills in ciProvider, runId, runAttempt, prNumber, prHeadCommit, branch, and commit automatically. Explicit arguments always win; pass detect_ci_environment=False to turn it off.

Parallel builds

from snapvisor import upload, ParallelConfig, finalize_builds

upload("screenshots", parallel=ParallelConfig(nonce="ci-run-42", total=4, index=2))

# When the shard count is not known up front (total=-1), finalize explicitly:
finalize_builds(parallel_nonce="ci-run-42")

skip_build() creates a skipped build so required status checks pass without a comparison, and find_baseline(commits=[...]) asks the API which commit can serve as a baseline.

The full API

from snapvisor import Snapvisor

sv = Snapvisor(token="<personal-access-token>")

me = sv.users.get_me()
project = sv.projects.get_project(owner="acme", project="web")
build = sv.builds.get_build(owner="acme", project="web", build_number="42")

for build in sv.builds.auto_paginate("listBuilds", owner="acme", project="web"):
    print(build.number, build.status)

All 37 operations the API publishes are reachable, grouped into namespaces: builds, comments, deployments, projects, reviews, changes, analytics, users, auth, repos. Both naming conventions resolve — sv.builds.list_builds(...) and sv.builds.listBuilds(...) are the same call.

Responses are typed models, not dict[str, Any]:

from snapvisor.models import Build, Project

Async

from snapvisor import AsyncSnapvisor

async with AsyncSnapvisor(token="...") as sv:
    me = await sv.users.get_me()
    async for build in sv.builds.auto_paginate("listBuilds", owner="acme", project="web"):
        print(build.number)

Which token do I need?

Only 8 of the 37 operations accept a project token (createBuild, updateBuild, finalizeBuilds, findBaseline, getAuthProject, the deployment operations, and the read-only build/project lookups). The other 29 require a personal access token (or an OAuth 2.1 access token). Snapvisor() prefers SNAPVISOR_PAT, then SNAPVISOR_TOKEN / ARGOS_TOKEN; using a project token where a PAT is required raises SnapvisorAuthError rather than a generic failure.

Static typing per operation

The facade dispatches dynamically, which is what keeps coverage in step with the API. When you want a type checker to see an individual operation's signature, import its module:

from snapvisor import Snapvisor
from snapvisor.operations import list_builds

sv = Snapvisor()
response = list_builds.sync_detailed(owner="acme", project="web", client=sv.raw)

Pagination

from snapvisor import Snapvisor

sv = Snapvisor()

# Everything, page by page, transparently:
for comment in sv.comments.auto_paginate(
    "listComments", owner="acme", project="web", build_number="42"
):
    print(comment.body)

# Or one page with its metadata:
page = sv.builds.page("listBuilds", owner="acme", project="web", per_page=50)
print(page.total, page.page, page.per_page, page.has_more)

per_page is clamped to the documented 1–100 range.

Retries and rate limits

from snapvisor import Snapvisor, RetryConfig, upload

retry = RetryConfig(
    max_retries=3,  # retries after the first attempt
    backoff_factor=0.5,  # exponential, with full jitter
    max_backoff=8.0,
    max_rate_limit_wait=60.0,  # longest sleep honoured for one 429
)

sv = Snapvisor(token="...", retry=retry)
upload("screenshots", retry=retry)
  • 5xx responses, connect errors, and timeouts are retried; POST/PATCH are replayed only when the connection never got established, so a completed write is never duplicated.
  • 4xx responses are never retried.
  • On 429 the SDK reads the draft-8 RateLimit / RateLimit-Policy headers (falling back to Retry-After), sleeps until the window resets, and resumes. If that wait exceeds max_rate_limit_wait, it raises SnapvisorRateLimitError carrying limit, remaining, and reset.
  • Every request carries a stable x-argos-request-id across retry attempts and an incrementing x-argos-retry-attempt; the request id is on every exception.

Errors

Exception When
SnapvisorConfigError Missing/invalid token, branch, commit, directory
SnapvisorAuthError 401 — token missing, malformed, or the wrong kind
SnapvisorForbiddenError 403 — missing scope or permission
SnapvisorNotFoundError 404
SnapvisorConflictError 409
SnapvisorRateLimitError 429 beyond the wait ceiling
SnapvisorServerError 5xx after retries were exhausted
SnapvisorUploadError A screenshot or trace was rejected by storage

All API errors subclass SnapvisorAPIError (so 0.1.0-era except SnapvisorAPIError still catches everything) and carry status_code, message, details (the server's per-field validation messages), and request_id.

CLI

snapvisor upload <directory>       Upload a directory of screenshots as one build
snapvisor skip                     Create a skipped build so required checks pass
snapvisor finalize                 Finalize every shard of a parallel build
snapvisor whoami                   Show the authenticated user
snapvisor build   list|get|diffs
snapvisor project list|get|create
snapvisor comment list|create|delete|resolve|unresolve
snapvisor review  list|create|dismiss
snapvisor change  ignore|unignore
snapvisor deployment get|resolve
snapvisor analytics
snapvisor login|logout             Store/remove a personal access token

Add --json for machine-readable output. Exit codes are differentiated so CI can branch on the failure kind: 0 success, 1 API error, 2 configuration error, 3 upload error, 4 rate limited.

Run snapvisor <command> --help for the flags of any command.

Environment variables

Every variable is read as SNAPVISOR_<NAME> first and ARGOS_<NAME> second, so existing Argos pipelines keep working unchanged. Explicit function/CLI arguments always take precedence over the environment.

Variable (either prefix) Purpose
…_TOKEN Project token used to authenticate (Authorization: Bearer …).
SNAPVISOR_PAT Personal access token, preferred by Snapvisor().
…_API_BASE_URL API base URL. Default https://api.snapvisor.io/v2/.
…_BRANCH Git branch. Falls back to CI detection, then git.
…_COMMIT Git commit SHA. Falls back to CI detection, then git.
…_BUILD_NAME Build name for multi-build setups.
…_PARALLEL true to enable parallel mode (with the vars below).
…_PARALLEL_NONCE Shared nonce across shards. Defaults to the CI run id.
…_PARALLEL_TOTAL Total number of shards.
…_PARALLEL_INDEX 1-based index of the current shard.
SNAPVISOR_CONFIG_DIR Where snapvisor login stores its token.

How it works

upload() implements the Snapvisor upload protocol:

  1. POST /v2/builds with the SHA-256 key and content type of every unique screenshot (and of every Playwright trace). The server replies with the build and signed upload targets for the files it does not already have, deduplicated by content hash.
  2. Each missing file is uploaded to its target — a proxied/presigned POST (with policy fields) or a presigned PUT — with a bounded pool of concurrent workers.
  3. PUT /v2/builds/{id} finalizes the build with the full screenshot list and returns the build, including its url.

Everything else is generated: snapvisor._generated is produced by openapi-python-client from https://api.snapvisor.io/v2/openapi.yaml, and a spec-drift CI job regenerates from the live spec on every run and fails on any diff. To regenerate locally:

pip install -e ".[dev,codegen]"
python scripts/regen.py

Never hand-edit src/snapvisor/_generated/ or src/snapvisor/_operations.py.

Relationship to Argos

Snapvisor is an independent visual-testing platform, forked from the open-source Argos project, which is MIT licensed. This SDK is original work, wire-compatible with the Argos upload protocol and deliberately compatible with its ARGOS_* environment variables and x-argos-* headers; it is built and maintained for Snapvisor, and is not affiliated with or endorsed by Argos.

License

MIT © 2026 Devino Solutions Inc.

Download files

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

Source Distribution

snapvisor-0.2.0.tar.gz (147.0 kB view details)

Uploaded Source

Built Distribution

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

snapvisor-0.2.0-py3-none-any.whl (264.3 kB view details)

Uploaded Python 3

File details

Details for the file snapvisor-0.2.0.tar.gz.

File metadata

  • Download URL: snapvisor-0.2.0.tar.gz
  • Upload date:
  • Size: 147.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for snapvisor-0.2.0.tar.gz
Algorithm Hash digest
SHA256 a87f538f24236e532c6380b02ff69c73e3855fdd8a2d631e53623f5df19187bd
MD5 929864b5600a51a71dc5d828b705016d
BLAKE2b-256 1651bd5d770c1a0c3d7fa17167ad2369fe0e107327973589823f2bff05b1f648

See more details on using hashes here.

Provenance

The following attestation bundles were made for snapvisor-0.2.0.tar.gz:

Publisher: publish.yml on DevinoSolutions/snapvisor-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 snapvisor-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: snapvisor-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 264.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for snapvisor-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 60b0b49447a32e5a1b1f1d963c4db766960eb8a02d07780c1f6b57094d90d3a5
MD5 3d98f550305d4fec398fb693917a15fb
BLAKE2b-256 ec8c45f9f72c7207d5aff97d5674147e669caafcd1ba7b7639f9bdfcf0609560

See more details on using hashes here.

Provenance

The following attestation bundles were made for snapvisor-0.2.0-py3-none-any.whl:

Publisher: publish.yml on DevinoSolutions/snapvisor-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