Api.Airforce
OAUTH PROVIDER

Sign in with Airforce

Let users authorise your app to access their Api.Airforce account. Standard OAuth 2.0 Authorization Code flow with PKCE.

Who this is for

You're building an app (playground, agent, mobile client, …) and want your users to plug in their Airforce account so they can run chat or image generation through your UI without sharing their raw API key with you. Use this flow.

You'll get an opaque access_token (24h TTL) scoped to whatever your app requested and the user approved. Use it as a Bearer header against /v1/* endpoints or /oauth/userinfo.

1. Get a client_id + client_secret

Phase 1 is admin-managed registration. Ping us with:

  • App name (shown on the consent screen to your users)
  • One-sentence description
  • Homepage URL (optional, shown on consent)
  • Square logo URL (optional)
  • One or more exact redirect URIs (https:// only, except http://localhost for dev)
  • Which scopes you need (see below)

We send you back client_id and a one-time client_secret. Store the secret server-side — if you're a pure SPA / native app without a backend, you can skip the secret and rely on PKCE alone.

2. Scopes

ScopeWhat it grants
profileRead the user object via /oauth/userinfo (id, username, plan, linked emails when verified).
chatPOST /v1/chat/completions, /v1/messages, /v1/messages/count_tokens, /v1/responses.
imagesPOST /v1/images/generations.
keys:readReserved — list the user's Airforce API keys (Phase 2).
keys:writeSENSITIVEReserved — create + revoke the user's Airforce API keys (Phase 2). Sensitive scope.

Multiple scopes are space-separated in the scope query parameter: scope=profile chat images.

3. The flow

3.1 Generate PKCE pair

In your client, before redirecting to /oauth/authorize, generate a random verifier and the matching SHA-256 challenge.

// In your app, before redirecting to /oauth/authorize:
const verifier = randomString(64); // 43..128 chars [A-Z][a-z][0-9]-._~
const challenge = base64UrlNoPad(sha256(verifier));
sessionStorage.setItem('airforce_pkce_verifier', verifier);

3.2 Redirect to /oauth/authorize

Build the authorize URL and send the user there. They'll see our consent screen, decide, and get redirected back to you.

https://api.airforce/oauth/authorize?
  response_type=code
  &client_id=airforce_xxxxxxxxxxxxxxxxxxxxxxxxx
  &redirect_uri=https://your.app/oauth/callback
  &scope=profile chat
  &state=<random opaque>
  &code_challenge=<base64url(sha256(verifier))>
  &code_challenge_method=S256

The user lands on our consent screen, signs in (if not already), and either approves or denies. We redirect back to your redirect_uri with a short-lived ?code=… on approval, ?error=access_denied on deny. Your original state is echoed back — verify it matches.

3.3 Exchange code for access_token

From your backend, swap the code (plus the PKCE verifier) for an access token.

curl -X POST https://api.airforce/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=https://your.app/oauth/callback" \
  --data-urlencode "code_verifier=$VERIFIER"

Response (24h TTL):

{
  "access_token": "airf_oat_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  "token_type": "Bearer",
  "expires_in": 86400,
  "scope": "profile chat"
}

3.4 Use the token

Call the userinfo endpoint or any /v1/* route the granted scopes allow:

# Profile lookup
curl https://api.airforce/oauth/userinfo \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# Chat (requires scope=chat)
curl https://api.airforce/v1/chat/completions \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"model":"gpt-4o-mini","messages":[{"role":"user","content":"Hi"}]}'

/oauth/userinfo response

{
  "id": "user-uuid",
  "username": "foo",
  "plan": "free",
  "is_admin": false,
  "email": "[email protected]",
  "email_verified": true,
  "github_email": "[email protected]",
  "discord_username": "foo#1234"
}

Fields like email, github_email, discord_username only appear when the user has actually verified / linked them. Plan for both presence and absence.

Revoking a token

When a user signs out of your app, revoke the token so it can't keep billing their account in the background:

curl -X POST https://api.airforce/oauth/revoke \
  --data-urlencode "token=$ACCESS_TOKEN"

RFC 7009. Always returns 200 regardless of whether the token existed (no oracle).

Security notes

  • PKCE is mandatory for public clients (no server-side client_secret). We accept S256 only — code_challenge_method=plain is rejected.
  • redirect_uri is exact-match. No prefix / wildcard matching. If you need multiple environments, register one URI per environment.
  • Tokens have a 24h TTL and no refresh tokens yet — when expired, run the user through /oauth/authorize again. For long-lived agents, prompt at startup and stash the token in secure storage.
  • Don't store client_secret in client-side code. If your app is a browser-only SPA or native client, register without a server and rely on PKCE.
  • Users can revoke your app any time from their dashboard → Apps tab. Handle 401 by re-running the OAuth flow.
  • Phase 1 coverage: OAuth bearer tokens work on /v1/chat/completions, /v1/messages, /v1/messages/count_tokens, /v1/responses, /v1/images/generations, and /oauth/userinfo. Audio, video, voice, and character endpoints still require a regular Airforce API key in Phase 1.

Get registered

Email [email protected] or ping us on Discord with the registration details listed above. We'll send back your credentials within a few hours.