OAuth
A developer application uses the Handle API for the people who allow it, with OAuth 2.0. It never sees their password. For your own scripts, a personal access token is simpler.
Register an application
Register it in Settings, Developer applications. Any account can register up to 3. You get a client ID, and choose a type, which can't be changed later:
- Public, for a mobile, desktop, single-page or command line app. It has no secret, because anything shipped to people's devices can be read. PKCE protects each sign-in.
- Confidential, for an app on a server you run. It also gets a client secret, shown once. Send it with HTTP Basic authentication on the token endpoint, never in the form. You can make a new secret from the application's page; the old one stops working at once.
List each redirect URI exactly. A request's redirect_uri must match one in full: no wildcards, no prefixes, no fragments. Allowed are https addresses, http on 127.0.0.1 or [::1] for a native app's local listener, where any port matches (not localhost), and a private-use scheme named after a domain you own, such as com.example.app:/callback. Only a public application can use the last two.
Get a token
Handle supports the authorization code flow with PKCE, using S256, for every application. There is no implicit or password grant. The metadata is at https://handle.pub/.well-known/oauth-authorization-server.
Send the person to the consent page with a fresh code challenge and the scopes you need:
https://handle.pub/oauth/authorize?response_type=code
&client_id=$CLIENT_ID
&redirect_uri=https://example.com/callback
&scope=profile:read
&state=$STATE
&code_challenge=$CHALLENGE
&code_challenge_method=S256
They sign in if they need to, see your application's name, who registered it, where access goes and each scope, and choose Allow or Deny. Handle then redirects to your URI with code, state and iss, or with error=access_denied. Check state and iss. The code works once, for 10 minutes. Exchange it at the token endpoint:
curl https://handle.pub/oauth/token \
-d grant_type=authorization_code \
-d code=$CODE \
-d redirect_uri=https://example.com/callback \
-d client_id=$CLIENT_ID \
-d code_verifier=$VERIFIER
A confidential application leaves out client_id and adds -u "$CLIENT_ID:$CLIENT_SECRET". The answer has an access_token that works for an hour and a refresh_token.
Scopes
The scopes are the API's own: blocks:read, blocks:write, privacy:read, privacy:write, profile:read, profile:write. A write scope brings its read scope with it. Ask for only what you use; the consent page lists each one. A scope never shows more than the API shows anyone: profile:read returns the visitor view of a profile.
Use and refresh it
Send the access token as Authorization: Bearer, as a personal access token is sent, to https://api.handle.pub. Each refresh, with grant_type=refresh_token, returns a new refresh token; keep the newest. The one before it still works until the new one is used, in case a response was lost, and any older one is refused. A connection ends 30 days after the person allowed it, and they are asked again.
Revocation
To sign a person out of your application, post the token to the token endpoint as token=… (RFC 7009). A person can revoke your application in Settings, Sign-in and security, and a password reset revokes every application. Deleting the application ends every connection it has. Its access tokens then answer 401 invalid_token, and its refresh tokens invalid_client.