🐍 airanks — the AIR Python SDK
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
- What is AIR?
- Install
- Quickstart
- API reference
- Shared authentication
- How auth resolution works
- Error handling
- Testing
- The AIR ecosystem
- License
📦 Install
pip install airanks
Requires Python 3.9+. One runtime dependency:
requests. Ships apy.typedmarker, so your type checker sees real annotations, notAnysoup.
📥 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 passapi_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.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file airanks-1.0.1.tar.gz.
File metadata
- Download URL: airanks-1.0.1.tar.gz
- Upload date:
- Size: 9.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
771cfe56fc0593687d70f4080128bdf48554181178963aed553c290cdfafe288
|
|
| MD5 |
e2f6e74d5951332119ab7bd6d9ff6590
|
|
| BLAKE2b-256 |
6d4db81665e2683ade0b2925bc1297b1a032f233ccff0cd9932b1277091ffcf5
|
Provenance
The following attestation bundles were made for airanks-1.0.1.tar.gz:
Publisher:
release.yml on airanks-net/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
airanks-1.0.1.tar.gz -
Subject digest:
771cfe56fc0593687d70f4080128bdf48554181178963aed553c290cdfafe288 - Sigstore transparency entry: 2482660557
- Sigstore integration time:
-
Permalink:
airanks-net/python-sdk@9c447fb15e4d8720c205a209549a2d881cc343df -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/airanks-net
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9c447fb15e4d8720c205a209549a2d881cc343df -
Trigger Event:
push
-
Statement type:
File details
Details for the file airanks-1.0.1-py3-none-any.whl.
File metadata
- Download URL: airanks-1.0.1-py3-none-any.whl
- Upload date:
- Size: 10.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ba1655751fa659680c39733d14433721aecdf59e8a4f9db5a1ecf224d61e30da
|
|
| MD5 |
c5c9e238ae351938008d477dba3a2185
|
|
| BLAKE2b-256 |
3242c2953e7ea13bce584c3b875d386e6a745a69281a0bf6e2c4c686a99c94c5
|
Provenance
The following attestation bundles were made for airanks-1.0.1-py3-none-any.whl:
Publisher:
release.yml on airanks-net/python-sdk
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
airanks-1.0.1-py3-none-any.whl -
Subject digest:
ba1655751fa659680c39733d14433721aecdf59e8a4f9db5a1ecf224d61e30da - Sigstore transparency entry: 2482660733
- Sigstore integration time:
-
Permalink:
airanks-net/python-sdk@9c447fb15e4d8720c205a209549a2d881cc343df -
Branch / Tag:
refs/tags/v1.0.1 - Owner: https://github.com/airanks-net
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@9c447fb15e4d8720c205a209549a2d881cc343df -
Trigger Event:
push
-
Statement type: