@airanks-net/sdk
@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/sdk
Requires 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 unauthenticated
See examples/lookup.mjs for a fuller example with error handling —
run it with npm run build && node examples/lookup.mjs.
How it works
Request & auth flow (click to expand)
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