OAuth 2.0
Authorization Code + PKCE for partner apps that need to act on behalf of FinzBooks users.
When to use it
Use OAuth instead of a PAT when:
- Your app is installed by users you don't control.
- You need per-user consent over which orgs / scopes you access.
- You want short-lived (1h) access + automatic refresh + revocation.
Flow overview
- You register an app and get
client_id+client_secret. - Your app redirects the user to
/oauth/authorizewith PKCE challenge. - User signs in to FinzBooks and grants the requested scopes.
- FinzBooks redirects back to your
redirect_uriwith an auth code. - Your server exchanges the code at
/oauth/tokenfor an access + refresh pair. - Use the access token. Rotate the refresh before the access expires.
Generate the PKCE pair
// Client side — generate a verifier + S256 challenge per authorize request.
const verifier =
base64UrlNoPad(crypto.getRandomValues(new Uint8Array(32)));
const challenge =
base64UrlNoPad(
new Uint8Array(
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(verifier))
)
);Authorize
GET https://api.aibooks.in/oauth/authorize
?response_type=code
&client_id=<your_client_id>
&redirect_uri=<your_redirect_uri>
&code_challenge=<challenge>
&code_challenge_method=S256
&scope=AIBooks.invoices.READ AIBooks.contacts.READ
&state=<csrf_token>
# &organization_id=<id> ← optional, binds the issued token to one orgOn consent, the user is redirected to:
<your_redirect_uri>?code=<auth_code>&state=<csrf_token>Exchange the code
POST https://api.aibooks.in/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=<auth_code>
&redirect_uri=<your_redirect_uri>
&code_verifier=<verifier>
&client_id=<your_client_id>
&client_secret=<your_client_secret>{
"access_token": "aibk_oat_…",
"refresh_token": "aibk_ort_…",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "AIBooks.invoices.READ AIBooks.contacts.READ"
}Refresh
POST https://api.aibooks.in/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token
&refresh_token=<refresh_token>
&client_id=<your_client_id>
&client_secret=<your_client_secret>Refresh tokens are single-use. Each rotation invalidates the previous refresh AND the access token it issued. Replaying a rotated refresh revokes the entire token family — a defence against stolen-token reuse.
Multi-org tokens
Omit organization_id at /oauth/authorize and the issued token is bound to the user, not a single org. Every API call must then pass ?organization_id=<id> to pick which org to target. The server verifies the user has an active membership in that org on every call.
Test your app
Once registered, click Generate Token on yourOAuth app to mint an access + refresh pair for yourself without going through the browser dance. Useful for integration tests + first-call debugging.