Onboarding with APIs.io
APIs.io publishes a machine-readable API Onboarding Descriptor at /.well-known/api-onboarding. It declares what it takes to get from a cold start to an authorized call here — the doors, the tiers, the credential shapes, and the places where the door is still shut.
This page is its human twin. Same facts, same gaps, in prose. If the two ever disagree, the descriptor is the one a machine reads, and the disagreement is a bug — tell us.
The path, end to end
Four steps. Step 1 works right now with nothing in your hands; steps 2 and 3 are where the callers diverge; step 4 is the same for everyone.
-
1
Everyone · no account, no key, no form
Discover, keyless
Prove the door is open with one unauthenticated call. Keyless reaches search, providers, APIs, tags, industries and the artifact collections.
HTTP no auth AOD
flow.steps.probe -
2
Choose by what you are, not by rank
Pick your door
Five registration mechanisms are declared, and they are not a ladder you climb in order — they are different callers. A human in a browser, a client registering an OAuth client, and an agent with nobody in the loop each take a different one.
open browser-oauth dcr cimd aauth
-
3
Where the doors differ
Take a credential
An API key on a browser sign-in, an OAuth client plus an authorization-code + PKCE token, or — for a signing agent — an agent principal with its own key and quota minted by a client-credentials call. Nothing here is one-time-display; keys are rotatable.
API key ·
X-API-KeyOAuth 2.0 + PKCE RFC 7591 DCR CIMD RFC 9421 · Ed25519 -
4
Everyone · same wire, same meters
Call it, and read your own budget
Send the credential, then read the limits off the response rather than guessing them. Tiers gate resources: above your tier is
402, over your meter is429, and it is never a403for either.RateLimit-Policy X-RateLimit-Tier MCP · same tiering
Step 1, in full
Discovery needs no account, no key and no form. Prove it in one call:
curl "https://apis.io/api/v1/search?q=sms"
Keyless reaches search, providers, APIs, tags, industries and the artifact collections. It is not
the whole API — ratings, capabilities, cohorts, insights, resolve, enrich, the playground list and
the service root are paid, and answer 402.
Do not probe the service root to test the door. GET /api/v1/ is itself gated and returns
402, so probing it proves the opposite of what you were asking.
Which door is yours?
browser-oauth
Sign in with GitHub, Google or LinkedIn. This is also the only door that reaches a paid tier.
dcr / cimd
Register an OAuth client dynamically, or skip registration with a client_id that is a URL. The authorize step still renders consent to a logged-in human.
aauth
Sign each request with your own Ed25519 key and present an aa-agent+jwt from an agent provider we allowlist.
Or take none of them: open is a door too, and it is the one that is already unlocked.
The five doors
| Mechanism | Who it is for | Technology | What you walk away with |
|---|---|---|---|
open |
anyone, including an agent, right now | plain HTTP, no auth | nothing to hold — keyless access to the catalog |
browser-oauth |
a human with a browser | GitHub / Google / LinkedIn sign-in → X-API-Key |
an API key |
dcr |
a client that wants an OAuth registration | RFC 7591 + authorization code & PKCE | an OAuth client for the MCP surface |
cimd |
a client whose client_id is a URL |
Client ID Metadata Document — no registration call | an OAuth client |
aauth |
an agent, with no human at any step | RFC 9421 Ed25519 signing + aa-agent+jwt |
an agent principal with its own key and quota |
open — no registration
Nothing to do. Call it.
browser-oauth — the human door
Sign in at apis.io/developer/account with GitHub, Google or
LinkedIn. A Learn key is issued free on sign-in; Understanding and Influence are
self-serve from the same page. Send the key as an X-API-Key header:
curl -H "X-API-Key: <your key>" "https://apis.io/api/v1/industries"
Rotate or revoke from the account page. There is no key-management endpoint in v1.
dcr and cimd — registering an OAuth client
The authorization server has supported both all along. Register through
POST https://apis.io/api/v1/auth/register, or skip registration entirely with a Client ID
Metadata Document. Either way you then run authorization-code + PKCE against
/api/v1/auth/authorize and /api/v1/auth/token, scopes apis:read and apis:pro. See
/.well-known/oauth-authorization-server.
The authorize step renders a consent page against a logged-in session, so a human is present for this one — which is exactly the gap the next door exists to close.
aauth — no registration, and no human
An agent signs its request with its own key (RFC 9421,
Ed25519) and presents an aa-agent+jwt in which its agent provider vouches for its
aauth:local@domain identifier. We verify both and recognise a caller we have never seen.
Signing alone earns attribution — the caller is recognised and audited as its identifier — but
no meter of its own: until it takes a principal it is limited per IP like any anonymous caller. To
hold an account, POST grant_type=client_credentials to the token endpoint with that same
signature. It mints an agent principal with its own API key and quota, a scope ceiling of
apis:read, no refresh token, and no way to self-grant paid data.
Anonymous callers are refused, and that is the point: the identifier costs an allowlisted agent
provider vouching for you, and an account that costs nothing to mint makes reputation over it
worthless. Reversible in one request — DELETE /api/v1/auth/me.
A bodied request must sign a content-digest; GET and HEAD need none. Resource metadata lives at
/.well-known/aauth-resource.json. The protocol is
draft-hardt-oauth-aauth-protocol.
This door is the one thing the descriptor states as a requirement. An allowlisted agent
provider vouching for your aauth:local@domain identifier is what establishes domain control, so
the onboarding descriptor declares it in the format’s own words rather than leaving you to infer it
from a rejection:
"verification": [
{ "facet": "domain_control", "min_grade": "verified", "blocking": true,
"vocabulary": "https://apis.io/kya/" }
]
min_grade is a floor, not an equality — a consumer holding verified satisfies a requirement
of attested. The other four doors declare nothing, because they require nothing: open,
browser-oauth, dcr and cimd are unaffected by this.
The technology it runs on
Nothing here is bespoke. Every step above is an open specification, and every one of them is declared at a well-known path a machine can read before it ever talks to us.
| Technology | What it does here | Where it is declared |
|---|---|---|
| API Onboarding Descriptor 0.1 | Declares this entire page in machine-readable form — mechanisms, plans, credential shapes, the probe step, the economics, and the gaps. | /.well-known/api-onboarding |
| API key over a header | The credential a browser sign-in issues, and the one an agent principal holds. Sent as X-API-Key; also read from APIS_IO_API_KEY. |
Authentication docs |
| OAuth 2.0 — authorization code + PKCE | /api/v1/auth/authorize and /api/v1/auth/token, scopes apis:read and apis:pro. Grants: authorization code, refresh token, client credentials. |
/.well-known/oauth-authorization-server |
| RFC 7591 — Dynamic Client Registration | Registers an OAuth client with no human, at POST /api/v1/auth/register. |
Authorization server metadata |
| Client ID Metadata Documents | Lets a client whose client_id is a URL skip registration altogether. |
Authorization server metadata |
| RFC 9421 — HTTP Message Signatures | Ed25519 request signing over at least @method, @path, @authority and signature-key. A bodied request also signs a content-digest. |
/.well-known/http-message-signatures-directory |
AAuth — draft-hardt-oauth-aauth-protocol |
Carries agent identity: an aa-agent+jwt in which an allowlisted agent provider vouches for an aauth:local@domain identifier. |
/.well-known/aauth-resource.json |
| KYA — Kin Score, Consumer 0.1 | The vocabulary the requirement above is written in: six facets, 26 checks, and four grades (verified, attested, declared, absent). Two numbers, never one — disclosure is what you told us, verification is what we confirmed. Graded facts, never a trust verdict. |
/kya/ · the API is deployed dark |
| RateLimit header fields | Every response states its own policy, limit, window and tier, so a caller reads its budget instead of guessing it. | On every response — see below |
| Model Context Protocol | The same catalog as an MCP server, on the same tiering and the same credentials. | /.well-known/mcp/server-card.json |
What each tier reaches
Tiers gate resources, not scopes. The full matrix is on the plans page, and that page is the source of truth — the boundary moves.
| Plan | Tier on the wire | Price | Rate | Daily quota |
|---|---|---|---|---|
| Explore | free | $0 | 5 req/s | 500 |
| Learn | starter | $0 | 20 req/s | 2,000 |
| Understanding | pro | $199/mo | 100 req/s | 10,000 |
| Influence | business | $499/mo | 400 req/s | 25,000 |
The plans are named for what you are doing with the catalog; the API still identifies them by their
original short names, and will keep doing so. The wire name is what you get in X-RateLimit-Tier,
in every 402 body, and in the x-tier extension on every operation in the reference — so if you
are reading a response rather than a page, that is the mapping you need.
A caller sending no key at all gets Explore access but no usage plan: it is limited per IP at the edge (3,000 requests / 5 minutes), because a plan meters a key and every anonymous caller would have to share one. Your limit does not move with anyone else’s traffic.
Read your own budget, don’t guess it
Every response carries its own limits:
ratelimit-policy: "quota";q=500;w=86400, "burst";q=5;w=1
x-ratelimit-tier: free
x-ratelimit-limit: 500
x-ratelimit-window: 86400
Two failures that are not the same failure
402 Payment Required— the resource is above your tier. More requests will not help; the body names the tier that returns it.429— you exceeded a rate or a daily quota at your own tier. Slow down and retry.
It is never a 403 for either.
The MCP server
https://apis.io/mcp shares exactly the same tiering. Searching the catalog,
reading a provider or an API, and browsing tags, taxonomy and artifacts are free and need no key.
Anything that synthesises across the catalog needs Understanding; benchmarking, comparison, gap
analysis, bulk export and requesting a check need Influence.
What is still shut
The descriptor carries these in its gaps array, and they belong on the human page too.
Only a paid tier still requires a human. An agent can go from cold discovery to an authorized
call with nobody in the loop — sign with AAuth, take a principal, hold a key and a quota. What it
cannot do is buy anything: the scope ceiling for an agent principal is apis:read, and paid
data answers 402. Machine payment is a separate problem and is deliberately not solved here.
(roadmap#105)
The aauth allowlist has exactly one agent provider in it, and we run it. A probe of 1,486
hosts on 2026-08-30 found no other AAuth resource document anywhere in the catalog. Read this as a
worked example, not an interoperating ecosystem.
maturity reads self-serve because that is the highest rung the AOD 0.1 enum has. It does
not distinguish a stranger with a browser from a stranger with no human at all, and those are now
two different doors here.
(roadmap#117)
A consumer cannot yet read its own record here. The descriptor’s verification[] block is no
longer empty — it states the one thing apis.io actually demands of a caller, domain_control at
grade verified, which is the requirement the aauth door has enforced all along and had no
machine-readable place to say. What is still shut is the other half: /api/v1/kya/me and
/api/v1/agents/{sub} are built and deployed dark, and answer 404 until the surface is
switched on. So a caller can learn what is demanded of it and cannot yet check whether it holds it.
Stating the requirement first is deliberate — claiming the record exists before it answers would be
the worse order.
(roadmap#110,
#112)
Publish your own
The descriptor is a small file at a well-known path, and it is the piece nothing else covers — OpenAPI describes what your API does, OAuth and DCR describe the credential handshake, and neither says what it takes to get there: the account, the plan gate, the terms, the verification queue, and the console a human still has to click through.
Read the spec and schema at apicommons.org/onboarding, then
serve yours at /.well-known/api-onboarding. APIs.io reads it — add your API and the
probe looks for your APIs.json, your onboarding descriptor, your OpenAPI, your llms.txt and your
MCP endpoint before it asks you to fill in anything.
Ours is at /.well-known/api-onboarding. Copy it, argue with it, or
tell us where it is wrong at github.com/api-search/apis-io.