npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

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

📦 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 pass apiKey (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

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)"]
  1. AIR_API_KEY env var (Node only) — explicit intent, always attaches.
  2. ~/.config/air/auth.json (Node only) — the file air login writes. A token loaded from here only attaches to requests aimed at the host it was saved for, so a repointed apiBase can't accidentally leak it elsewhere.
  3. Anonymous — no token, request rejected: the API returns 401 with error.code === "authentication_required" (the message includes the signup URL).

ℹ️ One login, every client. AIR_API_KEY env > ~/.config/air/auth.json > anonymous — the same three-step resolution runs in this SDK, the air CLI, 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 as apiKey to 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