{"_id":"@airanks-net/sdk","_rev":"2-210c087407b5e6ecf2d3f60c7b538b52","name":"@airanks-net/sdk","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.0":{"name":"@airanks-net/sdk","version":"1.0.0","keywords":["airanks","air","ai optimization","ai search","ai rankings","llm seo","sdk","api-client"],"author":{"url":"https://airanks.net","name":"Jeremy Schoemaker"},"license":"MIT","_id":"@airanks-net/sdk@1.0.0","maintainers":[{"name":"shoemoney","email":"jeremy@shoemoney.com"}],"homepage":"https://airanks.net","dist":{"shasum":"13e8f35bf5785e2345692a8f265e9d8118b852f2","tarball":"https://registry.npmjs.org/@airanks-net/sdk/-/sdk-1.0.0.tgz","fileCount":16,"integrity":"sha512-DNT2GeqW5ArSEYX7TIM/MOuyH3SM9xmVNGECjS2gMfBp/ClZEQVZQKX6snCTC26FfdjodpYuElzj6iPTgdgijg==","signatures":[{"sig":"MEUCIHEQQ+VSCfcNxx7t5NCYWTL/Li9qT8i2Nz5hz6d24kluAiEAx/Op7JMmeJLAWJOKkREkSeGUBfiRXKK3pIPGLWcnF/c=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":35068},"main":"./dist/cjs/index.js","type":"module","types":"./dist/types/index.d.ts","module":"./dist/esm/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"},"./package.json":"./package.json"},"gitHead":"f8abe0f92cef70f10016b5a27f0c43bacde152ef","scripts":{"build":"npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json","clean":"rm -rf dist","typecheck":"tsc -p tsconfig.json"},"_npmUser":{"name":"shoemoney","email":"jeremy@shoemoney.com"},"repository":{"url":"https://git.shoemoney.ai/shoemoney/airanks-oss.git","type":"git","directory":"js-sdk"},"_npmVersion":"12.0.2","description":"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.","directories":{},"sideEffects":false,"_nodeVersion":"26.7.0","_hasShrinkwrap":false,"devDependencies":{"typescript":"^5.6.0","@types/node":"^22.0.0"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.0_1786837664214_0.35984433846702735","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@airanks-net/sdk","version":"1.0.1","description":"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.","type":"module","main":"./dist/cjs/index.js","module":"./dist/esm/index.js","types":"./dist/types/index.d.ts","exports":{".":{"types":"./dist/types/index.d.ts","import":"./dist/esm/index.js","require":"./dist/cjs/index.js"},"./package.json":"./package.json"},"sideEffects":false,"engines":{"node":">=18"},"scripts":{"build":"npm run clean && tsc -p tsconfig.esm.json && tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json","clean":"rm -rf dist","typecheck":"tsc -p tsconfig.json"},"keywords":["airanks","air","ai optimization","ai search","ai rankings","llm seo","sdk","api-client"],"author":{"name":"Jeremy Schoemaker","url":"https://airanks.net"},"license":"MIT","homepage":"https://airanks.net","repository":{"type":"git","url":"https://git.shoemoney.ai/shoemoney/airanks-oss.git","directory":"js-sdk"},"devDependencies":{"@types/node":"^22.0.0","typescript":"^5.6.0"},"gitHead":"7f03c86032b4aed565eeb415fafcef6a2c709e09","_id":"@airanks-net/sdk@1.0.1","_nodeVersion":"26.7.0","_npmVersion":"12.0.2","dist":{"integrity":"sha512-Np3Pg1ow7fgAceyLhPh5OxYwC84HqQg9kPjjtFmEPrf68tI6QIw05NBW4D8XirFJsFOccnCQnMQlUzQ6Jxhwqg==","shasum":"5550b2651cb446a543b4a0b38c045a3e3f6ef504","tarball":"https://registry.npmjs.org/@airanks-net/sdk/-/sdk-1.0.1.tgz","fileCount":16,"unpackedSize":35913,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDfXdm8etBmNnLcCYlhpn79vvU350kIh2Zg3pd48y53QwIgTT3PbdmTXk7QTfCS5UYINEq5YIWH6BF0lnAiA7GlBdI="}]},"_npmUser":{"name":"shoemoney","email":"jeremy@shoemoney.com"},"directories":{},"maintainers":[{"name":"shoemoney","email":"jeremy@shoemoney.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_1.0.1_1786843943454_0.3830551344912436"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-15T23:47:44.027Z","modified":"2026-08-16T01:32:23.775Z","1.0.0":"2026-08-15T23:47:44.373Z","1.0.1":"2026-08-16T01:32:23.608Z"},"author":{"name":"Jeremy Schoemaker","url":"https://airanks.net"},"license":"MIT","homepage":"https://airanks.net","keywords":["airanks","air","ai optimization","ai search","ai rankings","llm seo","sdk","api-client"],"repository":{"type":"git","url":"https://git.shoemoney.ai/shoemoney/airanks-oss.git","directory":"js-sdk"},"description":"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":[{"name":"shoemoney","email":"jeremy@shoemoney.com"}],"readme":"# 🟩 @airanks-net/sdk\n\n**The official JavaScript / TypeScript SDK for the [AIR](https://airanks.net) API** — programmatic\naccess to AI Rank data for Node and the browser. Three methods, zero runtime dependencies, ships\nits own types.\n\n<p align=\"left\">\n  <img alt=\"npm version\" src=\"https://img.shields.io/npm/v/%40airanks-net%2Fsdk?color=2ea44f\">\n  <img alt=\"license\" src=\"https://img.shields.io/badge/license-MIT-2ea44f\">\n  <img alt=\"node\" src=\"https://img.shields.io/badge/node-%3E%3D18-2ea44f\">\n  <img alt=\"runtime deps\" src=\"https://img.shields.io/badge/runtime%20deps-0-2ea44f\">\n  <img alt=\"types\" src=\"https://img.shields.io/badge/types-included-2ea44f\">\n  <img alt=\"module formats\" src=\"https://img.shields.io/badge/module-ESM%20%2B%20CJS-2ea44f\">\n</p>\n\n## 🤔 What is AIR?\n\n**AIR (Artificial Intelligence Ranking)** by **airanks** makes **AI optimization** visible: how\noften, and how well, AI assistants like ChatGPT cite a given domain when answering real questions.\nIt's a 0–10 score per domain, backed by real observed citations, not a self-reported checklist.\n\nLook up any site's AIR score at **[airanks.net](https://airanks.net)**, or install the\n**[airanks toolbar](https://airanks.net/toolbar)** to see it while you browse.\n\nThis package is the JS/TS door into that same data — no CLI, no scaffolding, just a class with\nthree methods. 🚪\n\n## 📚 Table of Contents\n\n- [What is AIR?](#-what-is-air)\n- [Install](#-install)\n- [Quick Start](#-quick-start)\n- [How it works](#-how-it-works)\n- [Methods](#-methods)\n- [Auth — shared across every AIR client](#-auth--shared-across-every-air-client)\n- [Errors](#-errors)\n- [TypeScript](#-typescript)\n- [The `air` family](#-the-air-family)\n- [License](#-license)\n\n## 📦 Install\n\n```bash\nnpm install @airanks-net/sdk\n```\n\nRequires **Node 18+** (for global `fetch`) server-side; any modern browser client-side.\n\n## ⚡ Quick Start\n\n> ⚠️ Every request needs a token — get a free one at\n> **[airanks.net/tokens](https://airanks.net/tokens)**, then set `AIR_API_KEY` (Node) or pass\n> `apiKey` (browser). See [Auth](#-auth--shared-across-every-air-client) below.\n\n```ts\nimport { AirClient } from '@airanks-net/sdk';\n\n// Node: reads AIR_API_KEY, or ~/.config/air/auth.json written by `air login`.\nconst client = new AirClient();\n\nconst { data: domain } = await client.domain('stripe.com');\nconsole.log(domain.air_score); // 0-10\n\nconst { data: results } = await client.search('payment processing');\n\nconst who = await client.user(); // throws ApiError(401) if unauthenticated\n```\n\nSee [`examples/lookup.mjs`](examples/lookup.mjs) for a fuller example with error handling —\nrun it with `npm run build && node examples/lookup.mjs`.\n\n## 🧭 How it works\n\n<details>\n<summary><b>Request &amp; auth flow (click to expand)</b> 🖱️</summary>\n\n```mermaid\nsequenceDiagram\n    autonumber\n    participant App as Your code\n    participant SDK as AirClient\n    participant Auth as auth.ts\n    participant API as api.airanks.net\n\n    App->>SDK: new AirClient()\n    SDK->>Auth: resolve token (Node only)\n    Auth-->>SDK: AIR_API_KEY env, then ~/.config/air/auth.json, else anonymous\n\n    App->>SDK: client.domain('stripe.com')\n    SDK->>API: GET /v1/domains/stripe.com\n    alt first-ever lookup for this host\n        API-->>SDK: 200, ai_files.status = \"pending\"\n        loop poll (honors 429 Retry-After) until pollMaxMs\n            SDK->>API: GET /v1/domains/stripe.com\n            API-->>SDK: 200, still pending…\n        end\n        API-->>SDK: 200, ai_files.status = \"ready\"\n    else already hydrated\n        API-->>SDK: 200, ai_files.status = \"ready\"\n    end\n    SDK-->>App: { data, meta }\n```\n\n</details>\n\n`domain()` **always 200s** for a valid hostname. A never-before-seen domain triggers server-side\nhydration behind the scenes, so the SDK polls automatically while `ai_files.status === \"pending\"`,\nhonoring `429 Retry-After` along the way. If the poll budget (`pollMaxMs`, default 180s) runs out\nwhile still pending, it resolves with `pendingAtCap: true` instead of throwing — treat `air_score`\nas **unknown**, not a real zero, in that case.\n\n## 🛠️ Methods\n\n| Method | Returns | Notes |\n|---|---|---|\n| `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. |\n| `search(query)` | `{ data: { domains: [], brands: [], phrases: [] }, meta }` | Matches across everything AIR tracks. |\n| `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. |\n\n## 🔐 Auth — shared across every AIR client\n\nResolution order (first hit wins), **identical to every other AIR client** — the `air` CLI and the\nbrowser toolbar included — so logging in once with *any* of them authenticates this SDK too:\n\n```mermaid\nflowchart LR\n    A[\"🔑 AIR_API_KEY env var\\n(Node only, explicit intent)\"] -->|found| T[\"Attach Bearer token\"]\n    A -->|not set| B[\"📄 ~/.config/air/auth.json\\n(written by `air login`)\"]\n    B -->|found, host matches| T\n    B -->|not found| C[\"🚫 Anonymous\\n(401 authentication_required)\"]\n```\n\n1. **`AIR_API_KEY` env var** (Node only) — explicit intent, always attaches.\n2. **`~/.config/air/auth.json`** (Node only) — the file `air login` writes. A token loaded from\n   here only attaches to requests aimed at the host it was saved for, so a repointed `apiBase`\n   can't accidentally leak it elsewhere.\n3. **Anonymous** — no token, request rejected: the API returns `401` with\n   `error.code === \"authentication_required\"` (the message includes the signup URL).\n\n> ℹ️ **One login, every client.** `AIR_API_KEY` env > `~/.config/air/auth.json` > anonymous — the\n> same three-step resolution runs in this SDK, the `air` CLI, and the browser toolbar, so logging\n> in once works everywhere. There is no working anonymous fallback anymore — every caller except\n> the official browser toolbar needs a token.\n\n> 🔑 **A free account is required.** Every request through this SDK needs a token — grab one at\n> **[airanks.net/tokens](https://airanks.net/tokens)**, then set it via `AIR_API_KEY` (Node) or\n> pass it explicitly as `apiKey` to the client constructor (browser, or to override Node's\n> resolved token).\n\nIn the **browser**, this SDK never reads env vars or touches disk — pass a token explicitly:\n\n```ts\nconst client = new AirClient({ apiKey: 'your-air-token' });\n```\n\nPoint at a different API base (staging, a mirror, etc.) with `AIR_API_BASE` (Node) or the\n`apiBase` constructor option:\n\n```ts\nconst client = new AirClient({ apiBase: 'https://staging.airanks.net/api/v1' });\n```\n\n## 🚨 Errors\n\nNon-2xx responses reject with `ApiError`, which carries the HTTP status (`.status`) and, for a\n`429`, the server's `Retry-After` seconds (`.retryAfter`) when present:\n\n```ts\nimport { AirClient, ApiError } from '@airanks-net/sdk';\n\ntry {\n  const { data } = await client.domain('example.com');\n} catch (err) {\n  if (err instanceof ApiError && err.status === 429) {\n    // domain() already retries 429s internally up to its poll budget — this only\n    // fires if that budget is exhausted while still throttled.\n  }\n}\n```\n\n## 🧩 TypeScript\n\nShips its own `.d.ts` types — `Domain`, `AiFiles`, `SearchResults`, `AirUser`, `ApiError`, and\nmore are exported from the package root:\n\n| Export | Kind |\n|---|---|\n| `AirClient` | class |\n| `ApiError` | class |\n| `AirClientOptions`, `DomainOptions` | types |\n| `Domain`, `AiFiles`, `ResponseMeta` | types |\n| `DomainResponse`, `SearchResponse`, `SearchResults`, `SearchHit` | types |\n| `AirUser` | type |\n\nBoth **ESM** (`import`) and **CommonJS** (`require`) builds are published; pick either without\nconfiguration. Full contract details live in [`API-CONTRACT.md`](../API-CONTRACT.md) at the repo\nroot — the source of truth every `air` client (this SDK, the Node/Rust/Go CLIs, and the PHP\nComposer package) implements identically.\n\n## 🌐 The `air` family\n\nThis SDK is one client in the **airanks-net** open-source family, all speaking the same\n[API contract](../API-CONTRACT.md) and sharing the same login:\n\n| Client | What it is |\n|---|---|\n| [`node-cli`](../node-cli) | Reference `air` CLI implementation (Node) |\n| [`rust-cli`](../rust-cli) | `air` CLI in Rust |\n| [`go-cli`](../go-cli) | `air` CLI in Go |\n| [`python-sdk`](../python-sdk) | Python SDK |\n| [`composer-package`](../composer-package) | PHP/Composer package |\n| [`mcp-server`](../mcp-server) | Model Context Protocol server — AIR for agents |\n| [`chrome-extension`](../chrome-extension) | The [airanks toolbar](https://airanks.net/toolbar) |\n| [`homebrew-tap`](../homebrew-tap) | `brew install` for the CLIs |\n\n## 📄 License\n\n**MIT** — see [LICENSE](LICENSE).\n\n---\n\n<sub>Built for **AI optimization** by the folks at **airanks** 🟩 · one score, every AI · <a href=\"https://airanks.net\">airanks.net</a></sub>\n","readmeFilename":"README.md"}