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

StatusCodeMeaning
401missing_keyNo Authorization: Bearer header. A key in the query string does not count.
401invalid_keyThe key does not name an account. Check for a stray newline or quote.
403plan_requiredThe 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.

WhatWhere
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 endpointhttps://publicwww.com/oauth/authorize
Token endpointhttps://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_id inside 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_uris are https addresses, or http on 127.0.0.1, localhost or [::1] for an application running on the person's own computer - there any port matches. Custom schemes such as myapp:// are not accepted.
  • Every application is a public client: the token request carries no secret, whatever token_endpoint_auth_method the 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

WhereCodeMeaning
Authorizationerror pageThe 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.
Authorizationinvalid_requestNo code_challenge, or a method other than S256.
Authorizationunsupported_response_typeAnything but response_type=code.
Authorization, tokeninvalid_targetA resource other than the API.
Authorizationaccess_deniedThe person pressed Cancel.
Tokeninvalid_grantThe code is unknown, used, expired or issued to another client_id; or code_verifier or redirect_uri does not match.
Tokenunsupported_grant_typeAnything 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.

Next Making requests