Skip to main content

🐍 airanks — the AIR Python SDK

PyPI-ready python license typed deps

What is AIR? AIR (Artificial Intelligence Ranking) by airanks makes AI optimization visible — a 0–10 score for how often, and how well, an AI assistant like ChatGPT cites a given domain when answering real questions. Look up any site's AIR score at airanks.net, or install the browser toolbar to see it while you browse.

airanks is the Python door into that data: one client class, three methods, one dependency. No CLI, no scaffolding — pip install, import, go. 🚀


📚 Table of contents


📦 Install

pip install airanks

Requires Python 3.9+. One runtime dependency: requests. Ships a py.typed marker, so your type checker sees real annotations, not Any soup.

📥 Installing from source (until the PyPI release lands)
git clone https://git.shoemoney.ai/shoemoney/airanks-oss.git
cd airanks-oss/python-sdk
pip install -e .

⚡ Quickstart

from airanks import AirClient

client = AirClient()

domain = client.domain("stripe.com")
print(domain["data"]["air_score"])  # 0-10

results = client.search("payment processing")
who = client.user()  # raises ApiError(401) if unauthenticated

A fuller example with error handling lives in examples/lookup.py — run it with python examples/lookup.py stripe.com.


🔌 API reference

AirClient is the whole surface area. Three methods, all GET, all JSON in and out:

Method Returns Notes
client.domain(host: str) -> dict {"data": {...domain}, "meta": {"dataset_version": ...}} AIR score, percentile, and AI-file posture (llms.txt, ai.txt, robots.txt AI-agent rules, JSON-LD) for a hostname. Always 200s for a valid host — a never-before-seen domain triggers server-side hydration, so data["ai_files"]["status"] == "pending" means "check again shortly," not an error.
client.search(query: str) -> dict {"data": {"domains": [], "brands": [], "phrases": []}, "meta": {...}} Matches across everything AIR tracks.
client.user() -> dict The authenticated user (name, email) for whichever token was resolved. Raises ApiError with status_code == 401 if the token is missing, invalid, or revoked.

Constructor:

AirClient(api_key: str | None = None, api_base: str | None = None)
Arg Default Effect
api_key None → shared resolution order An explicit key takes priority over everything and always attaches, same as an env-sourced token.
api_base AIR_API_BASE env, else https://airanks.net/api/v1 Point at staging, a mirror, or a local dev server.

🔐 Shared authentication

One login works across every AIR client. Run air login once from the air CLI or the browser toolbar, and this SDK picks up the same token — no separate config, no re-auth.

Resolution order (first hit wins), identical across every AIR client — the air CLI, the browser toolbar, and every other language SDK in the ecosystem:

Priority Source Behavior
1️⃣ AIR_API_KEY env var Explicit intent — always attaches, to any host.
2️⃣ ~/.config/air/auth.json The file air login writes. Host-scoped: only attaches to requests aimed at the host it was saved for, so a repointed AIR_API_BASE can't accidentally leak a token elsewhere.
3️⃣ Anonymous No token. Subject to the anonymous rate limit.

Skip all of that and use a key unconditionally:

client = AirClient(api_key="your-token-here")

🧭 How auth resolution works

flowchart TD
    Start(["AirClient() constructed"]) --> Explicit{"api_key passed\nto constructor?"}
    Explicit -- yes --> UseExplicit["source = explicit\nalways attaches"]
    Explicit -- no --> Env{"AIR_API_KEY\nenv var set?"}
    Env -- yes --> UseEnv["source = env\nalways attaches"]
    Env -- no --> File{"~/.config/air/auth.json\nreadable + has token?"}
    File -- yes --> UseFile["source = file\nattaches ONLY if\nrequest host == saved host"]
    File -- no --> Anon["source = anonymous\nno Authorization header"]

    UseExplicit --> Request["client._get(url)"]
    UseEnv --> Request
    UseFile --> HostCheck{"urlparse(url).hostname\n== saved host?"}
    Anon --> Request
    HostCheck -- yes --> Attach["Authorization: Bearer <token>"]
    HostCheck -- no --> NoAttach["request sent unauthenticated"]
    Attach --> Request
    NoAttach --> Request

🚨 Error handling

Non-2xx responses (and transport failures — timeouts, DNS errors) raise airanks.ApiError:

from airanks import AirClient, ApiError

client = AirClient()
try:
    domain = client.domain("example.com")
except ApiError as e:
    if e.status_code == 429:
        time.sleep(e.retry_after or 30)
Attribute Type Meaning
status_code int | None HTTP status. None means a transport-level failure, not an HTTP response.
retry_after int | None Present on a 429 when the server sends a Retry-After header — seconds to wait.

The error message is pulled from the response body ({"error": {"message": ...}} or the 422 shape {"message": ...}), falling back to a generic "API returned {status_code}".


🧪 Testing

tests/test_auth.py covers token-resolution order and the host-scoped attach rule — no network calls, fully offline:

pip install -e ".[dev]"   # or: pip install -e . pytest
pytest

🌐 The AIR ecosystem

airanks (this package) is one door into AIR. Same API, same shared auth, different language:

Client What it is
🖥️ go-cli Zero-dependency Go CLI
🖥️ node-cli Node.js CLI
🦀 rust-cli Rust CLI
🐘 composer-package PHP client
📜 js-sdk JS/TS SDK for Node & browser
🧰 chrome-extension The AIR browser toolbar
🔌 mcp-server MCP server — air_rank / air_files / air_search tools for any agent
🤖 agent-toolkit Universal AI-agent toolkit (Claude, Codex, Cursor, Cline, …)

Why AIR: search used to be the whole game — now the traffic that matters is an AI assistant deciding whether to cite you at all, and that's a different AI optimization problem than classic SEO. AIR exists to make that measurable, with a 0–10 score backed by real observed citations, not a self-reported checklist. This client returns the same numbers airanks.net and the browser toolbar show — available from Python.


📄 License

MIT — see LICENSE.

Made with 🐍 + ☕ for anyone who wants their AI Rank without leaving Python.

Download files

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

Source Distribution

airanks-1.0.0.tar.gz (9.2 kB view details)

Uploaded Source

Built Distribution

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

airanks-1.0.0-py3-none-any.whl (9.8 kB view details)

Uploaded Python 3

File details

Details for the file airanks-1.0.0.tar.gz.

File metadata

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

File hashes

Hashes for airanks-1.0.0.tar.gz
Algorithm Hash digest
SHA256 fb731a335e135671f041e057fbf2f0b32312809b4305ae2a524e72c92a4f36a4
MD5 6ea57c906cbfaf2eff652ba2eb2ce6b9
BLAKE2b-256 f26ef1151d42fca1f6615671c169f967b4753fb6f1bf6fdc4844230e11d666d8

See more details on using hashes here.

Provenance

The following attestation bundles were made for airanks-1.0.0.tar.gz:

Publisher: release.yml on airanks-net/python-sdk

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

File details

Details for the file airanks-1.0.0-py3-none-any.whl.

File metadata

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

File hashes

Hashes for airanks-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c6c043db65d4184922956afdc85ac3e33191079f4fbcd9d25e0f82fc8b4bcb48
MD5 83b3d5ebb8d96f8106339bb176d1a038
BLAKE2b-256 d2742d0c1293a96b18e92b5a156085f0e7837da2d7e8c2435169ba77d153aaa7

See more details on using hashes here.

Provenance

The following attestation bundles were made for airanks-1.0.0-py3-none-any.whl:

Publisher: release.yml on airanks-net/python-sdk

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 Sentry Error logging StatusPage Status page