@airanks-net/sdk
v1.0.1
Published
JS/TS SDK for the AIR API (airanks.net) — AI optimization rankings: look up a domain's AIR score, search domains/brands/phrases, and check who's logged in. Works in Node and the browser.
Maintainers
Readme
🟩 @airanks-net/sdk
The official JavaScript / TypeScript SDK for the AIR API — programmatic access to AI Rank data for Node and the browser. Three methods, zero runtime dependencies, ships its own types.
🤔 What is AIR?
AIR (Artificial Intelligence Ranking) by airanks makes AI optimization visible: how often, and how well, AI assistants like ChatGPT cite a given domain when answering real questions. It's a 0–10 score per domain, backed by real observed citations, not a self-reported checklist.
Look up any site's AIR score at airanks.net, or install the airanks toolbar to see it while you browse.
This package is the JS/TS door into that same data — no CLI, no scaffolding, just a class with three methods. 🚪
📚 Table of Contents
- What is AIR?
- Install
- Quick Start
- How it works
- Methods
- Auth — shared across every AIR client
- Errors
- TypeScript
- The
airfamily - License
📦 Install
npm install @airanks-net/sdkRequires Node 18+ (for global fetch) server-side; any modern browser client-side.
⚡ Quick Start
⚠️ Every request needs a token — get a free one at airanks.net/tokens, then set
AIR_API_KEY(Node) or passapiKey(browser). See Auth below.
import { AirClient } from '@airanks-net/sdk';
// Node: reads AIR_API_KEY, or ~/.config/air/auth.json written by `air login`.
const client = new AirClient();
const { data: domain } = await client.domain('stripe.com');
console.log(domain.air_score); // 0-10
const { data: results } = await client.search('payment processing');
const who = await client.user(); // throws ApiError(401) if unauthenticatedSee examples/lookup.mjs for a fuller example with error handling —
run it with npm run build && node examples/lookup.mjs.
🧭 How it works
sequenceDiagram
autonumber
participant App as Your code
participant SDK as AirClient
participant Auth as auth.ts
participant API as api.airanks.net
App->>SDK: new AirClient()
SDK->>Auth: resolve token (Node only)
Auth-->>SDK: AIR_API_KEY env, then ~/.config/air/auth.json, else anonymous
App->>SDK: client.domain('stripe.com')
SDK->>API: GET /v1/domains/stripe.com
alt first-ever lookup for this host
API-->>SDK: 200, ai_files.status = "pending"
loop poll (honors 429 Retry-After) until pollMaxMs
SDK->>API: GET /v1/domains/stripe.com
API-->>SDK: 200, still pending…
end
API-->>SDK: 200, ai_files.status = "ready"
else already hydrated
API-->>SDK: 200, ai_files.status = "ready"
end
SDK-->>App: { data, meta }domain() always 200s for a valid hostname. A never-before-seen domain triggers server-side
hydration behind the scenes, so the SDK polls automatically while ai_files.status === "pending",
honoring 429 Retry-After along the way. If the poll budget (pollMaxMs, default 180s) runs out
while still pending, it resolves with pendingAtCap: true instead of throwing — treat air_score
as unknown, not a real zero, in that case.
🛠️ Methods
| Method | Returns | Notes |
|---|---|---|
| domain(host, options?) | { data, meta, pendingAtCap? } | AIR score, percentile, and AI-file posture (llms.txt, ai.txt, robots.txt AI-agent rules, JSON-LD) for a hostname. Auto-polls while hydrating. options.pollMs (default 20s) and options.pollMaxMs (default 180s) are tunable. |
| search(query) | { data: { domains: [], brands: [], phrases: [] }, meta } | Matches across everything AIR tracks. |
| user() | AirUser ({ name, email }) | The authenticated user for whichever token was resolved. Throws ApiError with status === 401 if the token is missing, invalid, or revoked. |
🔐 Auth — shared across every AIR client
Resolution order (first hit wins), identical to every other AIR client — the air CLI and the
browser toolbar included — so logging in once with any of them authenticates this SDK too:
flowchart LR
A["🔑 AIR_API_KEY env var\n(Node only, explicit intent)"] -->|found| T["Attach Bearer token"]
A -->|not set| B["📄 ~/.config/air/auth.json\n(written by `air login`)"]
B -->|found, host matches| T
B -->|not found| C["🚫 Anonymous\n(401 authentication_required)"]AIR_API_KEYenv var (Node only) — explicit intent, always attaches.~/.config/air/auth.json(Node only) — the fileair loginwrites. A token loaded from here only attaches to requests aimed at the host it was saved for, so a repointedapiBasecan't accidentally leak it elsewhere.- Anonymous — no token, request rejected: the API returns
401witherror.code === "authentication_required"(the message includes the signup URL).
ℹ️ One login, every client.
AIR_API_KEYenv >~/.config/air/auth.json> anonymous — the same three-step resolution runs in this SDK, theairCLI, and the browser toolbar, so logging in once works everywhere. There is no working anonymous fallback anymore — every caller except the official browser toolbar needs a token.
🔑 A free account is required. Every request through this SDK needs a token — grab one at airanks.net/tokens, then set it via
AIR_API_KEY(Node) or pass it explicitly asapiKeyto the client constructor (browser, or to override Node's resolved token).
In the browser, this SDK never reads env vars or touches disk — pass a token explicitly:
const client = new AirClient({ apiKey: 'your-air-token' });Point at a different API base (staging, a mirror, etc.) with AIR_API_BASE (Node) or the
apiBase constructor option:
const client = new AirClient({ apiBase: 'https://staging.airanks.net/api/v1' });🚨 Errors
Non-2xx responses reject with ApiError, which carries the HTTP status (.status) and, for a
429, the server's Retry-After seconds (.retryAfter) when present:
import { AirClient, ApiError } from '@airanks-net/sdk';
try {
const { data } = await client.domain('example.com');
} catch (err) {
if (err instanceof ApiError && err.status === 429) {
// domain() already retries 429s internally up to its poll budget — this only
// fires if that budget is exhausted while still throttled.
}
}🧩 TypeScript
Ships its own .d.ts types — Domain, AiFiles, SearchResults, AirUser, ApiError, and
more are exported from the package root:
| Export | Kind |
|---|---|
| AirClient | class |
| ApiError | class |
| AirClientOptions, DomainOptions | types |
| Domain, AiFiles, ResponseMeta | types |
| DomainResponse, SearchResponse, SearchResults, SearchHit | types |
| AirUser | type |
Both ESM (import) and CommonJS (require) builds are published; pick either without
configuration. Full contract details live in API-CONTRACT.md at the repo
root — the source of truth every air client (this SDK, the Node/Rust/Go CLIs, and the PHP
Composer package) implements identically.
🌐 The air family
This SDK is one client in the airanks-net open-source family, all speaking the same API contract and sharing the same login:
| Client | What it is |
|---|---|
| node-cli | Reference air CLI implementation (Node) |
| rust-cli | air CLI in Rust |
| go-cli | air CLI in Go |
| python-sdk | Python SDK |
| composer-package | PHP/Composer package |
| mcp-server | Model Context Protocol server — AIR for agents |
| chrome-extension | The airanks toolbar |
| homebrew-tap | brew install for the CLIs |
📄 License
MIT — see LICENSE.
Built for AI optimization by the folks at airanks 🟩 · one score, every AI · airanks.net
