Skip to content
TipPage Docs
Esc
↑↓navigate↵open⌘Jpreview
On this page

OAuth 2.0

Let other streamers connect your app to their TipPage with a Connect button - authorization code + PKCE, refresh tokens, and the same scopes as API keys.

For apps that other streamers use. They approve a consent screen and your app gets an access token that works everywhere an API key does - every /v1 endpoint, the realtime stream, the same scopes and rate limits. Standard OAuth 2.0: authorization code + PKCE (S256), rotating refresh tokens, RFC 8414 metadata. Any OAuth library works.

Authorize https://auth.tippage.com/oauth/authorize
Token https://auth.tippage.com/oauth/token
Revoke https://auth.tippage.com/oauth/revoke
Metadata https://auth.tippage.com/.well-known/oauth-authorization-server
Access token 1 hour
Refresh token Never expires; rotated on every use, ends only when revoked

The older api.tippage.com/oauth/token and /oauth/revoke still work.

1. Register your app

Settings -> Developer -> OAuth apps (owner and super admins, up to 10 apps):

  • Name and description - shown on the consent screen. Names that could pass for TipPage or a platform are refused.
  • Redirect URIs - exact match. https://, or http://localhost (any port), or a reverse-domain scheme (com.example.app:/callback). No wildcards.
  • Scopes - the most the app may ask for; each authorization requests a subset of the same scopes keys use.
  • Client type - confidential (server-side, gets a tpcs_ secret) or public (desktop/mobile/browser, no secret, PKCE required).

You get a client_id (oc_...).

2. Send the streamer to authorize

https://auth.tippage.com/oauth/authorize
  ?response_type=code
  &client_id=oc_...
  &redirect_uri=https://yourapp.example/oauth/callback
  &scope=tts:read%20tts:control%20chat:write
  &state=<random>
  &code_challenge=<BASE64URL(SHA256(code_verifier))>
  &code_challenge_method=S256

redirect_uri can be left out only when the app has exactly one. PKCE is required for public clients and checked whenever a challenge is sent; only S256 is accepted. Make a fresh verifier per authorization:

import crypto from "node:crypto";
const verifier = crypto.randomBytes(32).toString("base64url");
const challenge = crypto.createHash("sha256").update(verifier).digest("base64url");

Only the account owner or a super admin can approve. They come back to your redirect_uri with ?code=...&state=... (single use, 10 minutes) or ?error=access_denied. Other request errors also come back as ?error=, except an unknown client_id or unregistered redirect_uri, which are shown on the consent page and never redirected.

3. Exchange the code

Form body (JSON also accepted). Confidential clients authenticate with HTTP Basic or client_secret in the body; public clients send client_id.

curl https://auth.tippage.com/oauth/token -u "oc_...:tpcs_..." \
  -d grant_type=authorization_code -d code=... \
  -d redirect_uri=https://yourapp.example/oauth/callback -d code_verifier=...
{ "access_token": "tpat_...", "token_type": "Bearer", "expires_in": 3600,
  "refresh_token": "tprt_...", "scope": "tts:read tts:control chat:write" }

Use the access token like a key. GET /v1/me says whose TipPage it is, credential.type: "oauth", the granted scopes, and your app in oauth.

4. Refresh

curl https://auth.tippage.com/oauth/token -u "oc_...:tpcs_..." \
  -d grant_type=refresh_token -d refresh_token=tprt_...

You get a new access token and a new refresh token; store both, the old refresh token is dead. Presenting a rotated-out refresh token again revokes the whole authorization (it’s treated as leaked).

Revocation

  • The streamer: Settings -> Developer -> Connected apps -> Revoke. Tokens die and realtime sockets close within seconds.
  • Your app: POST /oauth/revoke with token=<access or refresh token> and your client credentials (RFC 7009). Either token ends the whole authorization. Unknown tokens still return 200.

Rotating your client secret revokes nothing. Removing a scope from the app’s registration revokes every connection; adding scopes or redirect URIs doesn’t. Re-authorizing replaces the previous connection (one per app per streamer). Official TipPage apps are the exception: each approval is its own connection, so the OBS dock can be signed in on two machines at once, and each shows as its own row to revoke.

Everything else

  • Realtime: send the access token as apiKey on the stream. The socket lives as long as the authorization, not the access token.
  • Events your app causes carry actor: { "type": "oauth_app", "id": "oc_...", "name", "authorization_id": "oa_..." }.
  • Errors: { "error", "error_description" }. invalid_client is 401; invalid_grant, invalid_scope, unsupported_grant_type are 400. A /v1 call with a dead access token gets 401 invalid_token - refresh, and if that fails with invalid_grant, send the streamer through authorize again.
  • Never put a confidential client’s secret in a browser or a shipped binary - register a public client.

Was this page helpful?