Skip to main content

Official Python SDK for the Vairified Partner API

Project description

Vairified Python SDK

Official Python SDK for the Vairified Partner API
Multi-sport player ratings, search, and bulk match submission

CI Version 0.2.0 Python 3.12+ PyPI


Async-first Python SDK for the Vairified Partner API. Built on httpx and pydantic v2, with a surface designed to feel native: properties instead of getters, dict-like access to rating splits, auto-paginating search, and a sub-resource layout that mirrors the REST API.

Installation

pip install vairified

Or with uv:

uv add vairified

Quick Start

import asyncio
from vairified import Vairified

async def main() -> None:
    async with Vairified(api_key="vair_pk_xxx") as client:
        member = await client.members.get("vair_mem_xxx")
        print(member.name, "rated", member.rating_for("pickleball"))

asyncio.run(main())

The client is an async context manager — opening it creates an httpx.AsyncClient, closing it releases the underlying connection pool.

Sub-resources

Every operation lives on a sub-resource that matches the REST path:

Sub-resource Operations
client.members get, search, find, rating_updates
client.matches submit, test_webhook
client.oauth authorize, exchange_token, refresh, revoke
client.leaderboard list, rank, categories
client.usage() Rate-limit + request-count stats

Members

Get a connected member

async with Vairified(api_key="vair_pk_xxx") as client:
    member = await client.members.get("vair_mem_xxx")

    print(member.name)                      # Full name
    print(member.display_name)               # "Mike B."
    print(member.rating_for("pickleball"))   # 3.915
    print(member.status.is_vairified)        # True

    # Dict-like access to rating splits for a specific sport
    pb = member.sport["pickleball"]
    print(pb.rating, pb.abbr)                # 3.915 VO
    print(pb["overall-open"].rating)         # 3.915
    print("singles-open" in pb)              # True
    for key, split in pb:
        print(key, split.rating)

Filter ratings to specific sports

# Just pickleball
member = await client.members.get("vair_mem_xxx", sport="pickleball")

# Multiple sports
member = await client.members.get(
    "vair_mem_xxx",
    sport=["pickleball", "padel"],
)

Auto-paginating search

search() is an async iterator — it streams results one member at a time, fetching pages lazily as you iterate. break early when you have what you need, or cap with max_results.

async with Vairified(api_key="vair_pk_xxx") as client:
    async for member in client.members.search(
        city="Austin",
        state="TX",
        rating_min=3.5,
        rating_max=4.5,
        vairified_only=True,
    ):
        print(member.name, member.rating_for("pickleball"))

    # Find first N across pages
    top_20: list = []
    async for m in client.members.search(name="Smith", max_results=20):
        top_20.append(m)

Find by name (first hit only)

mike = await client.members.find("Mike Barker")
if mike:
    print(mike.rating_for("pickleball"))

Rating change notifications

updates = await client.members.rating_updates()
for update in updates:
    arrow = "↑" if update.improved else "↓"
    print(f"{update.display_name} {arrow} delta={update.delta:+.3f}")

Match Submission

Matches are submitted as a MatchBatch — defaults at the batch level apply to every match unless overridden. The shape is n-team × n-game, so singles, doubles, and round-robin all go through the same path.

from vairified import Vairified, MatchBatch, Match, Game

async with Vairified(api_key="vair_pk_xxx") as client:
    batch = MatchBatch(
        sport="pickleball",
        win_score=11,
        win_by=2,
        bracket="4.0 Doubles",
        event="Weekly League",
        match_date="2026-04-11T14:00:00Z",
        matches=[
            Match(
                identifier="m1",
                teams=[["vair_mem_aaa", "vair_mem_bbb"],
                       ["vair_mem_ccc", "vair_mem_ddd"]],
                games=[Game(scores=[11, 8]), Game(scores=[11, 5])],
            ),
            Match(
                identifier="m2",
                teams=[["vair_mem_eee"], ["vair_mem_fff"]],   # singles
                games=[Game(scores=[11, 9]), Game(scores=[11, 7])],
            ),
        ],
    )
    result = await client.matches.submit(batch)
    if result.ok:
        print(f"Submitted {result.num_games} games in {result.num_matches} matches")

Set batch.dry_run = True to validate without persisting — your API key must have the dry-run scope.

OAuth Connect Flow

import secrets
from vairified import Vairified, OAuthError

async with Vairified(api_key="vair_pk_xxx") as client:
    # Step 1 — start authorization
    state = secrets.token_urlsafe(32)
    auth = await client.oauth.authorize(
        redirect_uri="https://myapp.com/oauth/callback",
        scopes=["profile:read", "rating:read", "match:submit"],
        state=state,
    )
    redirect_to = auth.authorization_url

    # Step 2 — exchange the callback code
    tokens = await client.oauth.exchange_token(
        code="code-from-callback",
        redirect_uri="https://myapp.com/oauth/callback",
    )
    access = tokens.access_token
    refresh = tokens.refresh_token
    player_id = tokens.player_id

    # Step 3 — refresh when the access token expires
    try:
        new_tokens = await client.oauth.refresh(refresh)
    except OAuthError as e:
        if e.error_code == "invalid_grant":
            ...  # user must re-authorize

    # Step 4 — revoke the connection
    await client.oauth.revoke(player_id)

Available scopes

Scope Description
profile:read Name, location, verification status
profile:email Email address
rating:read Current rating and rating splits
rating:history Complete rating history
match:submit Submit matches on behalf of user
webhook:subscribe Rating change notifications

Leaderboards

async with Vairified(api_key="vair_pk_xxx") as client:
    # Global doubles leaderboard
    lb = await client.leaderboard.list()

    # Texas singles, verified only
    tx = await client.leaderboard.list(
        category="singles",
        scope="state",
        state="TX",
        verified_only=True,
        limit=50,
    )

    # A specific player's rank with 5 players on either side
    rank = await client.leaderboard.rank(
        "vair_mem_xxx",
        category="doubles",
        context_size=5,
    )
    print(f"#{rank['rank']} (top {rank['percentile']:.1f}%)")

    # Available categories, brackets, scopes
    categories = await client.leaderboard.categories()

Models

All response models are immutable pydantic v2 BaseModels. They accept both snake_case (Python) and camelCase (wire) field names, and tolerate new fields from the server so your code keeps working across API additions.

Member

member.member_id                     # Numeric member ID
member.id                            # UUID
member.name                          # Full name (property)
member.display_name                  # "Mike B."
member.first_name                    # "Mike"
member.last_name                     # "Barker"
member.gender                        # Gender enum (MALE | FEMALE | OTHER | UNKNOWN)
member.age
member.city / state / zip / country
member.status.is_vairified           # Grouped status flags
member.status.is_connected
member.sport                         # dict[str, SportRating]
member.sports                        # ["pickleball", "padel"] (property)
member.rating_for("pickleball")      # float | None
member.split("overall-open")         # RatingSplit | None

SportRating (dict-like)

pb = member.sport["pickleball"]
pb.rating                     # Primary rating for this sport
pb.abbr                       # "VO", "VG", etc.
pb["overall-open"].rating     # Any split key
len(pb)                       # Number of splits
"singles-40+" in pb           # Membership check
for key, split in pb: ...     # Iterate (yields (key, RatingSplit) tuples)

Match / MatchBatch

Request-side models use camelCase aliases (winScore, winBy, matchDate) when serialized — write Python in snake_case, the wire stays consistent.

batch = MatchBatch(
    sport="pickleball",
    win_score=11,
    win_by=2,
    match_date="2026-04-11T14:00:00Z",
    matches=[
        Match(
            identifier="m1",
            teams=[["p1", "p2"], ["p3", "p4"]],   # n-team × n-player
            games=[Game(scores=[11, 8]),           # n-game match
                   Game(scores=[11, 5])],
        ),
    ],
)

RatingUpdate

update.member_id
update.previous_rating
update.new_rating
update.delta        # new - previous (or None)
update.improved     # True if delta > 0
update.changed_at

Configuration

from vairified import Vairified

# Environment preset
client = Vairified(api_key="vair_pk_xxx", env="production")   # default
client = Vairified(api_key="vair_pk_xxx", env="staging")
client = Vairified(api_key="vair_pk_xxx", env="local")

# Custom base URL (overrides env)
client = Vairified(
    api_key="vair_pk_xxx",
    base_url="http://localhost:3001/api/v1",
    timeout=30.0,
)

Environment variables

export VAIRIFIED_API_KEY="vair_pk_xxx"
export VAIRIFIED_ENV="staging"   # optional; default: production
async with Vairified() as client:   # reads both env vars
    ...

API Key Scopes

Scope Access
admin Full access to all endpoints
write All read + write operations
read All read operations
leaderboard:read Leaderboard endpoints only
player:search Player search only
member:read Connected member data only
match:submit Submit match results
tournament:import Import tournament data
dry-run Validate writes without persisting

Error Handling

from vairified import (
    Vairified,
    VairifiedError,
    RateLimitError,
    AuthenticationError,
    NotFoundError,
    ValidationError,
    OAuthError,
)

async with Vairified(api_key="vair_pk_xxx") as client:
    try:
        member = await client.members.get("vair_mem_xxx")
    except RateLimitError as e:
        print(f"Rate limited; retry after {e.retry_after}s")
    except AuthenticationError:
        print("Invalid API key")
    except NotFoundError:
        print("Member not found")
    except ValidationError as e:
        print(f"Bad request: {e.message}")
    except OAuthError as e:
        print(f"OAuth error: {e.message} (code: {e.error_code})")
    except VairifiedError as e:
        print(f"API error: {e.message} (status: {e.status_code})")

Migrating from 0.1.x

Version 0.2.0 is a breaking rewrite. See the migration guide for the full diff, and CHANGELOG.md for the release notes.

Development

git clone https://github.com/Vairified/vairified.py.git
cd vairified.py
uv sync --all-extras
uv run pytest

License

MIT — see LICENSE for details.


vairified.com · Documentation · Support

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

vairified-0.2.0.tar.gz (82.5 kB view details)

Uploaded Source

Built Distribution

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

vairified-0.2.0-py3-none-any.whl (23.5 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for vairified-0.2.0.tar.gz
Algorithm Hash digest
SHA256 684bdbc1774274d3aab34eed6d1b9ce96b49f3f1506db6de0bd713d1bb8cd988
MD5 1c9acfc169a505fdd13f809add3b0af3
BLAKE2b-256 7e205dd321a78295b6f9189bd547503b5d5fb895456b9cc26bec12436aa5bdc3

See more details on using hashes here.

Provenance

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

Publisher: release.yml on Vairified/vairified.py

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

File details

Details for the file vairified-0.2.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for vairified-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c2323656d54f20ecdc37977070da14d2aa0820c0fb083b036cbead6e54cc82ef
MD5 01ebe4b477dd14006ee78cede61b5f53
BLAKE2b-256 b541c7221254ae3608ed4bf7624fb69921eda84eb51e84a38daa3abaaa875c33

See more details on using hashes here.

Provenance

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

Publisher: release.yml on Vairified/vairified.py

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