Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🐍 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
Loading

🚨 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.

About

AIR Python SDK — airanks AI optimization API client (pip install airanks). airanks.net

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages