Authentication
One header, on every request except the self-describing index.
Authorization: Bearer <your api key>
Tokens are issued on your profile page - up to ten
per account, each revocable on its own, so a leaked one can be deleted without
touching the others. A paid plan is required: without one, every
endpoint except / and /v1/account answers
403 plan_required.
An application can also get a token for you through OAuth 2.1: you sign in, see what it asks for and press Allow. Its token goes in the same header and works the same way.
Why not ?key=
A key in the query string ends up in places you did not put it: web server
access logs, browser history, proxy logs, and the Referer header
of anything the response links to. The API therefore does not accept it, and
answers 401 missing_key saying so.
The old ?export= urls on the main site do still accept
?key=, because scripts written years ago depend on it and taking
it away would break them. That is the one place it lives - see
the old export urls.
Checking a key works
/v1/account is the cheapest call: it spends no quota and works
even on an account with no plan, so it answers both "is this key valid" and
"what am I entitled to".
curl -H "Authorization: Bearer $KEY" https://api.publicwww.com/v1/account
{
"plan": "enterprise",
"plan_until": 1819461840,
"full_access": true,
"quota": {
"searches": { "limit": 300, "used": 12, "resets_at": 1787961600 },
"snippets": { "limit": 100, "used": 3, "resets_at": 1787961600 }
},
"limits": {
"disclosed_positions": 4294967295,
"max_per_page": 1000000,
"max_per_page_snippets": 10000
}
}
What can go wrong
| Status | Code | Meaning |
|---|---|---|
| 401 | missing_key | No Authorization: Bearer header. A key in the query string does not count. |
| 401 | invalid_key | The key does not name an account. Check for a stray newline or quote. |
| 403 | plan_required | The key is fine; the account has no paid plan. |
A 401 also carries a WWW-Authenticate: Bearer header,
so http clients that handle authentication generically behave sensibly.
OAuth 2.1 for applications
An application that works on behalf of other people - an assistant, an
integration, a hosted service - should not ask each of them to copy a token.
It sends them to PublicWWW instead: they sign in, approve the application,
and it receives a token of its own. That token is sent as
Authorization: Bearer like any other and opens the whole API and
the MCP server at https://api.publicwww.com/mcp, under the
account's plan, quota and rate limit.
| What | Where |
|---|---|
| Authorization server metadata (RFC 8414) | https://publicwww.com/.well-known/oauth-authorization-server |
| Protected resource metadata (RFC 9728) | https://api.publicwww.com/.well-known/oauth-protected-resource |
| Authorization endpoint | https://publicwww.com/oauth/authorize |
| Token endpoint | https://publicwww.com/oauth/token |
| Revocation endpoint (RFC 7009) | https://publicwww.com/oauth/revoke |
Identifying the application
There is no client registration. The client_id is the https URL
of a small JSON document the application publishes - a client metadata
document. PublicWWW reads it every time someone connects, so the name and
return addresses are always current, and the person approving sees which
host published them.
{
"client_id": "https://app.example.com/oauth/client.json",
"client_name": "Example App",
"redirect_uris": ["https://app.example.com/oauth/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "none"
}
-
client_idinside the document must be exactly the URL it is served from. The document is fetched over https, from a URL with a path, without following redirects; it must answer within 5 seconds and stay under 64 KB. -
redirect_urisare https addresses, or http on127.0.0.1,localhostor[::1]for an application running on the person's own computer - there any port matches. Custom schemes such asmyapp://are not accepted. -
Every application is a public client: the token request carries no
secret, whatever
token_endpoint_auth_methodthe document names. The authorization code is protected by PKCE instead.
The flow
Authorization code with PKCE; S256 is the only method. Send the
person to the authorization endpoint:
https://publicwww.com/oauth/authorize
?response_type=code
&client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient.json
&redirect_uri=https%3A%2F%2Fapp.example.com%2Foauth%2Fcallback
&code_challenge=<BASE64URL(SHA-256(code_verifier))>
&code_challenge_method=S256
&state=<random>
They sign in with a one-time code sent by email if they have not already,
see the application's name, the host of its document and where they will
return, and press Allow or Cancel. Back at redirect_uri come
code, your state and
iss=https://publicwww.com (RFC 9207). A code is valid for ten
minutes and works once. Exchange it:
curl https://publicwww.com/oauth/token \
-d grant_type=authorization_code \
-d code="$CODE" \
-d code_verifier="$VERIFIER" \
-d client_id=https://app.example.com/oauth/client.json \
-d redirect_uri=https://app.example.com/oauth/callback
{ "access_token": "<token>", "token_type": "Bearer", "scope": "mcp" }
scope may be left out: there is one scope, mcp, and
it covers the whole API. resource (RFC 8707) may be left out
too; if sent, it is https://api.publicwww.com/mcp or
https://api.publicwww.com.
How long a token lives
Until it is revoked - there is no expiry and no refresh token. An integration that works today keeps working tomorrow without anyone touching it. A token is revoked only on purpose: the person disconnects the application on their profile page, the application revokes it itself, or the account is deleted.
curl https://publicwww.com/oauth/revoke \
-d token="$TOKEN" \
-d client_id=https://app.example.com/oauth/client.json
The revocation endpoint always answers 200, whether or not the
token existed.
OAuth errors
| Where | Code | Meaning |
|---|---|---|
| Authorization | error page | The client_id document could not be read, or redirect_uri is not listed in it. The person is not sent back: an address that was not verified is never followed. |
| Authorization | invalid_request | No code_challenge, or a method other than S256. |
| Authorization | unsupported_response_type | Anything but response_type=code. |
| Authorization, token | invalid_target | A resource other than the API. |
| Authorization | access_denied | The person pressed Cancel. |
| Token | invalid_grant | The code is unknown, used, expired or issued to another client_id; or code_verifier or redirect_uri does not match. |
| Token | unsupported_grant_type | Anything but authorization_code. |
Authorization errors other than the error page come back to
redirect_uri as error, error_description,
state and iss; token errors are a 400
with the same two fields in JSON.