The PHP client for the AIR API.
AIR (Artificial Intelligence Ranking) by airanks makes AI optimization visible: a 0–10 score for how often, and how well, AI assistants like ChatGPT cite a domain when answering real questions. Check any site's AIR score at airanks.net, or install the airanks toolbar to see it while you browse. This package is the PHP door into that same data — no CLI, no scaffolding, one class, three methods.
- What is AIR?
- Install
- Quickstart
- How a request resolves
- API reference
- 🔑 Shared authentication
- Errors
- Testing
- Sibling AIR clients
- License
AIR (Artificial Intelligence Ranking) by airanks is AI optimization made visible —
the AI-era answer to "where do I rank." Instead of guessing whether ChatGPT knows and trusts your
site, AIR gives you a number: a 0–10 score backed by real observed citations, plus your AI-file
posture (llms.txt, ai.txt, robots.txt AI-agent rules). Look up any domain free at
airanks.net, or install the
browser toolbar to see the score on every page you visit. This
package puts the same numbers behind a PHP Client class.
composer require airanks-net/api-clientRequires: PHP >=8.2, ext-curl, ext-json.
use Airanks\ApiClient\Client;
$client = new Client();
$domain = $client->domain('stripe.com');
echo $domain['data']['air_score']; // 0-10
$results = $client->search('payment processing');
$who = $client->user(); // throws ApiException(401) if unauthenticatedA never-before-seen domain still 200s and triggers server-side hydration in the background —
$domain['data']['ai_files']['status'] === 'pending' means "check again shortly," not an
error.
📄 Fuller example, with error handling (examples/lookup.php)
<?php
declare(strict_types=1);
require __DIR__ . '/../vendor/autoload.php';
use Airanks\ApiClient\ApiException;
use Airanks\ApiClient\Client;
// Auth resolves itself: AIR_API_KEY env, then ~/.config/air/auth.json (written by `air login`
// in any AIR client), then anonymous. Pass a token explicitly if you'd rather not rely on either.
$client = new Client();
try {
$result = $client->domain('stripe.com');
$domain = $result['data'];
printf("%s — AIR %d/10\n", $domain['hostname'], $domain['air_score']);
if (($domain['ai_files']['status'] ?? null) === 'pending') {
echo "still gathering — check back in a minute\n";
}
$search = $client->search('payment processing');
printf("%d domains matched \"payment processing\"\n", count($search['data']['domains'] ?? []));
$who = $client->user();
echo "logged in as: " . ($who['data']['name'] ?? $who['name'] ?? 'anonymous') . "\n";
} catch (ApiException $e) {
fwrite(STDERR, "AIR request failed ({$e->getStatusCode()}): {$e->getMessage()}\n");
exit(1);
}Run it with php examples/lookup.php (after composer install).
flowchart TD
A["new Client($token = null)"] --> B{Explicit token\npassed to constructor?}
B -- yes --> C["source: explicit\nalways attaches"]
B -- no --> D["Auth::resolveToken()"]
D --> E{AIR_API_KEY\nenv set?}
E -- yes --> F["source: env\nalways attaches"]
E -- no --> G{"~/.config/air/auth.json\nreadable + has token?"}
G -- yes --> H["source: file\nattaches only if request\nhost === saved host"]
G -- no --> I["source: anonymous\nno Authorization header"]
C --> J["Client::get($url)\ncurl + User-Agent + Bearer (if attached)"]
F --> J
H --> J
I --> J
J --> K{HTTP status}
K -- "< 400" --> L["decode JSON → array"]
K -- ">= 400" --> M["throw ApiException\n(message, status, retryAfter for 429)"]
Airanks\ApiClient\Client — one constructor, three request methods:
| Method | Returns | Notes |
|---|---|---|
new Client(?string $token = null, ?string $apiBase = null) |
— | $token skips shared auth resolution entirely and always attaches. $apiBase overrides AIR_API_BASE / the default https://airanks.net/api/v1. |
domain(string $host): array |
{"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. |
search(string $q): array |
{"data": {"domains": [], "brands": [], "phrases": []}, "meta": {...}} |
Matches across every domain, brand, and phrase AIR tracks. |
user(): array |
The authenticated user (name, email) for whichever token was resolved. |
Throws ApiException with status 401 if the token is missing, invalid, or revoked. |
This package resolves auth exactly the way every other AIR client does, in this order — first hit wins:
| # | Source | Behaviour |
|---|---|---|
| 1 | AIR_API_KEY env var |
Explicit intent — always attaches, to any host. |
| 2 | ~/.config/air/auth.json |
The file air login writes. Only attaches to requests whose host matches the host it was saved for, so a repointed AIR_API_BASE can't leak a stale token elsewhere. |
| 3 | Anonymous | No token sent; subject to the anonymous rate limit. |
Because the path, env var, and file shape are identical across clients, air login run once —
from the CLI or the toolbar — authenticates this library too. Nothing to configure beyond an
optional explicit token:
$client = new Client(token: getenv('MY_OWN_AIR_TOKEN'));Point at a different API base (staging, a mirror, etc.) with the AIR_API_BASE env var, or the
constructor's second argument.
Non-2xx responses throw Airanks\ApiClient\ApiException, which carries the HTTP status
(getStatusCode()) and, for a 429, the server's Retry-After seconds (getRetryAfter()) when
present:
use Airanks\ApiClient\ApiException;
try {
$domain = $client->domain('example.com');
} catch (ApiException $e) {
if ($e->getStatusCode() === 429) {
sleep($e->getRetryAfter() ?? 30);
}
}tests/AuthTest.php covers token-resolution order and the host-scoped attach rule — no network
calls involved.
composer install
composer testEvery client below shares the same auth file, so logging in with any one of them logs you into all the others:
| Client | Language | Repo |
|---|---|---|
air |
Go CLI | github.com/airanks-net/go-cli |
air-cli |
Node CLI | github.com/airanks-net/node-cli |
air-cli |
Rust CLI | github.com/airanks-net/rust-cli |
@airanks-net/sdk |
JS/TS SDK | github.com/airanks-net/js-sdk |
airanks |
Python SDK | github.com/airanks-net/python-sdk |
airanks-mcp-server |
MCP server | github.com/airanks-net/mcp-server |
air-toolbar |
Chrome extension | github.com/airanks-net/chrome-extension |
MIT — see LICENSE.
Built for the AIR ecosystem at airanks.net — because knowing your AI optimization score beats guessing it. 🎯