OAuth 2.0 flow
ZetPin uses the OAuth 2.0 authorization-code grant. Redirect the user to consent, receive a single-use code, and exchange it for scoped tokens.
Endpoints
| Purpose | Method | URL |
|---|---|---|
| Authorize (user consent) | GET | https://api.zetpin.com/oauth/authorize |
| Token exchange & refresh | POST | https://api.zetpin.com/oauth/token |
| Revoke a token | POST | https://api.zetpin.com/oauth/revoke |
The authorization-code flow
- Your app redirects the user to
/oauth/authorizewith yourclient_id,redirect_uri,response_type=code, the requestedscope, and a randomstate. Public clients also send a PKCEcode_challenge. - The user signs in with a phone OTP on ZetPin's consent page and approves the requested scopes.
- ZetPin redirects back to your
redirect_uriwith a single-usecode(prefixzpac_, 5-minute TTL) and your originalstate. - Your server POSTs the code to
/oauth/tokenwithgrant_type=authorization_codeto receive azpat_access token andzprt_refresh token. - You call
/dev/v1endpoints with the access token as a Bearer credential, and refresh before it expires.
Confidential vs public clients
How your app authenticates at the token endpoint depends on whether it can keep a secret.
| Client type | Where it runs | Token-endpoint auth |
|---|---|---|
| Confidential | Server-side (backend you control) | client_secret — PKCE optional but recommended |
| Public | Mobile apps, SPAs, anything shipped to users | PKCE code_verifier — mandatory, no secret |
PKCE is mandatory for public clients
Public clients cannot hold aclient_secret safely, so they must use PKCE (S256) to bind the authorization code to the app that requested it. See PKCE for how to generate the verifier and challenge.redirect_uri must match exactly
The redirect_uri you send on /oauth/authorize and again on /oauth/token must be a byte-for-byte match of a value registered for your app — including scheme, host, port, path, and any trailing slash. A mismatch is rejected before consent. Register every callback URL you intend to use.
Use state for CSRF protection
Generate an unguessable state value per authorization request, store it in the user's session, and send it on /oauth/authorize. ZetPin echoes it back on the redirect. Reject the callback if the returned state doesn't match what you stored — this prevents cross-site request forgery on your callback.
Example: authorize request
GET https://api.zetpin.com/oauth/authorize
?client_id=zp_app_xxx
&redirect_uri=https://yourapp.com/callback
&response_type=code
&scope=profile.read posts.write posts.read
&state=8f3c9a2e
&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
&code_challenge_method=S256Example: redirect back to your app
# On approval
GET https://yourapp.com/callback?code=zpac_a1b2c3d4...&state=8f3c9a2e
# If the user declines consent
GET https://yourapp.com/callback?error=access_denied&state=8f3c9a2eNext
Continue to PKCE to build your challenge, or jump to Refreshing & revoking for the token lifecycle.