One authenticated GET endpoint. Send any domain or URL; get back the verified page-type map for that domain — the login, checkout, pricing, docs, and contact URLs your agent policy needs to make an allow/deny decision before the request leaves your network. This page is the complete reference: authentication, parameters, response schema, errors, quotas, and runnable examples.
Subscribe to any plan on the pricing page, copy the API key from your account area, and run this. That is the whole integration surface — there are no SDKs to install and no other endpoints to learn.
curl -H "X-API-Key: YOUR_API_KEY" \ "https://www.aiagentallowlist.com/api/check?domain=stripe.com"
The response is a single JSON object: whether the domain is in the database, its primary language, the page-type map with one verified URL per confirmed page type, and how many lookups remain on your plan this cycle. Everything below is detail on that one request and its one response.
Every plan includes an API key, issued when your subscription is activated — you will find it in your account area the moment payment completes. Send it with each request either as the X-API-Key header or as an api_key query parameter; both are accepted on every request.
# Recommended: header authentication curl -H "X-API-Key: YOUR_API_KEY" \ "https://www.aiagentallowlist.com/api/check?domain=stripe.com" # Alternative: query-parameter authentication curl "https://www.aiagentallowlist.com/api/check?domain=stripe.com&api_key=YOUR_API_KEY"
X-API-Key header in production. Query strings — and any keys embedded in them — routinely end up in proxy logs, web-server access logs, and browser histories. The query-parameter form exists for quick tests and environments where setting headers is awkward.A request with no key, or with a key that does not match an account, returns 401. A valid key on an account that is not yet activated — or whose monthly quota is exhausted — returns 403 with an explanatory message. Details in the error reference below.
The endpoint is GET https://www.aiagentallowlist.com/api/check over HTTPS. It takes one required parameter and one optional authentication parameter.
| Parameter | Required | Description |
|---|---|---|
domain |
required | The domain to look up. Accepts a bare domain or a full URL — scheme, path, port, and a leading www. are stripped server-side, so domain=stripe.com and domain=https://dashboard.stripe.com/login both resolve to stripe.com. This means you can pass the exact URL your agent is about to open without normalizing it first. A value that cannot be parsed into a domain returns 400. |
api_key |
optional | Your API key, if you are not sending it as the X-API-Key header. One of the two must be present on every request. |
domain value, percent-encode it (encodeURIComponent, urlencode, or your HTTP client’s params mechanism) so its own query string does not get mixed into the API request’s. All four code samples below do this correctly.Successful lookups return 200 with a JSON object. The shape is identical whether the domain is found or not — only found and the contents of page_types change — so one parser handles both cases.
| Field | Type | Meaning |
|---|---|---|
domain | string | The normalized domain the lookup resolved to, after stripping scheme, path, port, and www. Always echo-check this against what you sent. |
found | boolean | true if the domain is in the classified database; false if it is not. For a guardrail, false is itself a signal — see the not-found example below. |
language | string | Detected primary language of the site as an ISO 639-1 code (e.g. en). Present on found domains. |
page_types | object | Map of page-type name → verified URL on the domain. Keys are drawn from the 20 page types listed below. Only types that actually exist on the domain appear — if a key is absent, the classification pipeline confirmed there is no such page. Empty object ({}) when found is false. |
quota_remaining | integer | Lookups left on your plan in the current 30-day cycle, after this request. Returned on every metered response, found or not, so you never need a separate usage call. |
page_types keys are drawn from a fixed vocabulary of 20 page types, including:
The two highlighted types — login and checkout — are the credential and transaction surfaces most agent policies deny by default. The full page-type definitions are on the database page.
Each key maps to the verified live URL — discovered by traversing the site’s actual link structure, not guessed from path patterns.
{
"domain": "0-0-8studios.com",
"found": true,
"language": "en",
"page_types": {
"login": "http://0-0-8studios.com/m/account",
"contact": "http://0-0-8studios.com/contact-us",
"about": "http://0-0-8studios.com/about-0-0-8",
"careers": "http://0-0-8studios.com/careers",
"legal": "http://0-0-8studios.com/privacy-policy"
},
"quota_remaining": 89999
}
Still 200, still one credit — and for a guardrail, a verdict in its own right: an unclassified domain is a default-deny candidate.
{
"domain": "example-unknown.com",
"found": false,
"page_types": {},
"quota_remaining": 89998
}
page_types on a found domain means the pipeline confirmed the domain has no such page; a domain returning found: false means the classification layer has never seen it. Treat the first as “this surface does not exist here” and the second as “this destination is unvetted” — under a default-deny posture, the second should block or escalate.Errors use conventional HTTP status codes with a JSON body carrying a human-readable message. Quota-related 403s also include quota fields so your client can distinguish “not activated” from “used up”.
| Code | Meaning | What your client should do |
|---|---|---|
| 200 | Success | Parse the JSON. Check found before reading page_types. |
| 400 | Invalid domain parameter | The value was missing or could not be parsed into a domain. Fix the input; do not retry unchanged. |
| 401 | Missing or invalid API key | No key was sent, or the key matches no account. Verify the key against your account area; do not retry until corrected. |
| 403 | Account not activated, or monthly quota exhausted | Read the JSON message and quota fields to tell the cases apart. Not activated: complete payment/activation. Quota exhausted: wait for the next 30-day cycle or upgrade — there is no automatic overage billing. |
| 429 | Too many requests | You hit the per-IP burst guard (roughly 240 requests/minute, independent of your quota). Back off briefly and retry; smooth bursts with a local queue. |
| 503 | Backend temporarily unavailable | Transient. Retry with exponential backoff. In a guardrail, fail closed or serve from your local cache rather than skipping the check. |
Each sample reads the key from an environment variable, sends it as a header, passes the domain safely encoded, and handles the found / not-found / error cases. Copy, set AAL_API_KEY, run.
# Bare domain curl -H "X-API-Key: $AAL_API_KEY" \ "https://www.aiagentallowlist.com/api/check?domain=stripe.com" # Full URL — let curl handle the encoding with --data-urlencode + -G curl -G -H "X-API-Key: $AAL_API_KEY" \ --data-urlencode "domain=https://dashboard.stripe.com/login" \ "https://www.aiagentallowlist.com/api/check"
import os import requests API_URL = "https://www.aiagentallowlist.com/api/check" HEADERS = {"X-API-Key": os.environ["AAL_API_KEY"]} def check_domain(domain): # `domain` may be a bare domain or a full URL — the API normalizes it resp = requests.get(API_URL, headers=HEADERS, params={"domain": domain}, timeout=10) if resp.status_code == 429 or resp.status_code == 503: raise RuntimeError(f"Transient error {resp.status_code} — retry with backoff") resp.raise_for_status() return resp.json() record = check_domain("https://dashboard.stripe.com/login") if record["found"]: login_url = record["page_types"].get("login") print(f"login page: {login_url}, quota left: {record['quota_remaining']}") else: print("unclassified domain — default-deny candidate")
const API_URL = "https://www.aiagentallowlist.com/api/check"; async function checkDomain(domain) { const url = `${API_URL}?domain=${encodeURIComponent(domain)}`; const res = await fetch(url, { headers: { "X-API-Key": process.env.AAL_API_KEY } }); if (res.status === 429 || res.status === 503) { throw new Error(`Transient error ${res.status} — retry with backoff`); } if (!res.ok) throw new Error(`API error ${res.status}`); return res.json(); } const record = await checkDomain("https://dashboard.stripe.com/login"); if (record.found) { console.log("login page:", record.page_types.login ?? "none on this domain"); console.log("quota left:", record.quota_remaining); } else { console.log("unclassified domain — default-deny candidate"); }
<?php function check_domain($domain) { $url = 'https://www.aiagentallowlist.com/api/check?domain=' . urlencode($domain); $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, CURLOPT_HTTPHEADER => ['X-API-Key: ' . getenv('AAL_API_KEY')], ]); $body = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); curl_close($ch); if ($status === 429 || $status === 503) { throw new RuntimeException("Transient error $status — retry with backoff"); } if ($status !== 200) { throw new RuntimeException("API error $status: $body"); } return json_decode($body, true); } $record = check_domain('https://dashboard.stripe.com/login'); if ($record['found']) { echo 'login page: ', $record['page_types']['login'] ?? 'none on this domain', PHP_EOL; echo 'quota left: ', $record['quota_remaining'], PHP_EOL; } else { echo 'unclassified domain — default-deny candidate', PHP_EOL; }
The canonical deployment: your agent framework, gateway, or proxy intercepts every URL an agent wants to open, resolves the domain against the API, and applies page-type policy before the request is sent. Cache each domain’s record locally — agents revisit the same domains constantly, and one cached lookup covers every URL on that domain.
# Policy: which page types this fleet may touch ALLOW_TYPES = {"pricing", "docs", "blog", "about", "contact"} DENY_TYPES = {"login", "checkout"} def evaluate_navigation(url): domain = extract_domain(url) # or just pass the full URL as `domain` record = cache.get(domain) # local cache first — one lookup per domain if record is None: record = api_check(domain) # GET /api/check?domain=... cache.set(domain, record, ttl=CACHE_TTL) if not record["found"]: return DENY("unclassified domain") # default-deny: unknown = unvetted page_type = match_url_to_type(url, record["page_types"]) if page_type in DENY_TYPES: return DENY(f"denied page type: {page_type}") # log + alert if page_type in ALLOW_TYPES: return ALLOW(page_type) return FLAG(url) # unmatched URL on a known domain → review
Three practices make this pattern hold up in production. Cache aggressively: keying the cache by domain means a fleet that opens ten thousand URLs a day on five hundred domains spends five hundred lookups, not ten thousand. Log every verdict — allow, deny, and flag alike — with the page type and URL, so denials feed alerts and allows feed policy reviews. Keep the default-deny branch honest: the found: false path is where the 2026-style incidents get stopped, so route it to denial or human review, never to silent allow. Broader policy design — fleet scoping, chokepoints, audit trails — is covered in the enterprise guardrails guide and on the agent guardrails page.
Each successfully served lookup — found or not — consumes one credit. Quotas run on 30-day cycles from your subscription date, and every metered response carries quota_remaining. Separately from the quota, a coarse per-IP burst guard of roughly 240 requests per minute smooths traffic spikes; hitting it returns 429 and does not consume credits.
| Plan | Price / month | Lookups / month | |
|---|---|---|---|
| Pro | $99 | 90,000 | Subscribe → |
| Pro Plus | $249 | 225,000 | Subscribe → |
| Advanced | $499 | 450,000 | Subscribe → |
| Advanced Plus | $999 | 900,000 | Subscribe → |
| Business | $1,997 | 2,750,000 | Subscribe → |
| Enterprise | $3,999 | 10,000,000 | Subscribe → |
Exhausted quotas return 403 until the next cycle — there is no automatic overage billing and your card is never charged beyond the plan price. Need more than 10M lookups a month, or want the data on-prem with no metering at all? See database licenses and OEM options on the pricing page.
/api/check with a domain parameter returns whether the domain is in the database, its detected primary language, and a page_types object mapping each confirmed page type — login, checkout, pricing, docs, contact, about, careers, blog, legal, and the rest of the 20-type vocabulary — to its verified URL on that domain. Every metered response also includes quota_remaining.X-API-Key header (recommended for production — it keeps the key out of URL logs) or as an api_key query parameter. Keys are issued on subscription and shown in your account area the moment your plan is activated.found: false mean for a guardrail?page_types — did the check fail?found: true response means the classification pipeline confirmed the domain has no such page — a verified negative your policy can rely on, not a coverage gap.www are stripped server-side, so domain=stripe.com and domain=https://dashboard.stripe.com/login both resolve to stripe.com. Just make sure the value is URL-encoded so the passed URL’s own query string is not mixed into the API request’s.Plans start at $99/month for 90,000 lookups. Subscribe, pay by PayPal or card, and your key is active the moment payment completes.