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

🔑 Every method now requires a token — a free account gets you one at airanks.net/tokens. Set it via AIR_API_KEY (or pass api_key= below) before calling anything; see Shared authentication.

import os
from airanks import AirClient

os.environ["AIR_API_KEY"] = "your-token-here"  # or export it in your shell
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 the token is missing, invalid, or revoked

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

A free account is required. Anonymous requests now get a 401 authentication_required — grab a token at airanks.net/tokens, then set it via AIR_API_KEY or pass api_key= to AirClient(). 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 attached — the API now rejects these with 401 authentication_required. Every method requires a token from one of the two sources above (or an explicit api_key=).

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\n→ 401 authentication_required"]

    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\n→ 401 authentication_required"]
    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.

Release files for airanks 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for airanks 1.0.1
File Size Uploaded
airanks-1.0.1.tar.gz 9.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for airanks 1.0.1
File Interpreter ABI Platform
airanks-1.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 19.5 kB

Release files / airanks-1.0.1.tar.gz

Download URL airanks-1.0.1.tar.gz
Size 9.4 kB
Tags Source
SHA-256 checksum
How to use checksums
771cfe56fc0593687d70f4080128bdf48554181178963aed553c290cdfafe288
BLAKE2b-256 checksum
How to use checksums
6d4db81665e2683ade0b2925bc1297b1a032f233ccff0cd9932b1277091ffcf5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 16, 2026.

Transparency log

Release files / airanks-1.0.1-py3-none-any.whl

Download URL airanks-1.0.1-py3-none-any.whl
Size 10.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ba1655751fa659680c39733d14433721aecdf59e8a4f9db5a1ecf224d61e30da
BLAKE2b-256 checksum
How to use checksums
3242c2953e7ea13bce584c3b875d386e6a745a69281a0bf6e2c4c686a99c94c5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page