Implementing OAuth 2.0
Pennylane supports OAuth 2.0, an industry-standard protocol that allows applications to request access to user data without requiring user credentials.
This guide explains how to implement OAuth 2.0 with Pennylane: registering your app, authorizing users, exchanging and refreshing tokens, and revoking access when no longer needed.
Use OAuth if you are:
- An integration partner building a third-party app.
- An accounting firm accessing the Firm API with firm-level tokens.
Company customers should use API tokens instead (see Generating a Company API Token)
OAuth 2.0 Flow Overview
The process follows a standard Authorization Code flow:
(1) User clicks Sign in with Pennylane in your app.
(2) User approves access → Pennylane returns an authorization code.
(3) Your app exchanges the code for an access token (and refresh token).
(4) Use the access token in API calls.
(5) Refresh the token when it expires.
(6) Revoke the token when access is no longer needed.
For background reading, see this document.
Token Types & Resource Scope
Company Token
Granted for a single company. Access is limited to that company’s resources, bounded by the requested/approved scopes (e.g., read/write products, invoices).
Firm Token
Granted at the firm level. Can access resources across multiple client companies managed by the firm. The exact list of companies (including confidential ones) depends on who granted access and their internal permissions. Scopes still apply, but visibility is filtered by the granting user’s access.
In short: scopes define what you can do, while the token context (company vs firm) and granting user define which resources you can access.
Examples
- With a company token: GET /api/external/v2/products → products for that company only.
- With a firm token: GET /api/external/v2/companies → companies visible to the granting firm user; subsequent calls must be made in the context of a selected company.
Step 1. Register Your Application
Before using OAuth, register your app with Pennylane by contacting our Partnerships team.
(1) Contact the Partnerships team via this form.
(2) Once validated, you will receive a Client ID and Client Secret.
The Client Secret must be kept secure and never shared.
Store both Client ID and Client Secret immediately, as they cannot be retrieved later. If they are lost, a new OAuth app must be created, which may take time.
Step 2. Add a “Sign in with Pennylane” Button
Redirect users to:
https://app.pennylane.com/oauth/authorize
GET Parameters
Parameter | Required | Description |
---|---|---|
client_id | yes | ID provided when your app is registered |
redirect_uri | yes | URL where the user is redirected after approval |
response_type | yes | Must be code |
scope | yes | Space-separated list of scopes requested |
state | no | Unique string to prevent CSRF attacks |
The user is prompted to select one of their available companies or firms and grant access.
Step 3. Exchange Code for Access Token
After approval, Pennylane redirects to your redirect_uri with a code (and state). Verify that the state matches what you sent to prevent tampering. Then exchange the code for tokens.
curl -X POST https://app.pennylane.com/oauth/token \
-d "client_id=YOUR_CLIENT_ID" \
-d "client_secret=YOUR_CLIENT_SECRET" \
-d "code=AUTH_CODE" \
-d "redirect_uri=YOUR_REDIRECT_URI" \
-d "grant_type=authorization_code"
Body Parameters
Parameter | Required | Description |
---|---|---|
client_id | yes | Your Client ID |
client_secret | yes | Your Client Secret |
code | yes | Authorization code received in Step 2 |
redirect_uri | yes | Must match the one used in Step 2 |
grant_type | yes | Must be authorization_code |
Example response
{
"access_token": "abc123...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "def456..."
}
Step 4. Refresh the Token
Access tokens expire (default: 24h). Use the refresh token to get a new one:
curl -X POST https://app.pennylane.com/oauth/token
Body parameters
Parameter | Required | Description |
---|---|---|
client_id | yes | Your Client ID |
client_secret | yes | Your Client Secret |
refresh_token | yes | Refresh token received with the access token |
grant_type | yes | Must be refresh_token |
Refresh tokens are valid for 90 days. Once used, a refresh token is revoked and replaced.
Step 5. Revoke the Token
When access is no longer needed, revoke the token:
POST https://app.pennylane.com/oauth/revoke
Body parameters
Parameter | Required | Description |
---|---|---|
client_id | yes | Your Client ID |
client_secret | yes | Your Client Secret |
token | yes | The access or refresh token to revoke |
Returns HTTP 200 OK with an empty body if successful.
Best Practices
✅ Always validate the state parameter.
✅ Store token creation time and refresh before expiration.
✅ Request only the minimal scopes required (principle of least privilege).
✅ Keep your Client Secret secure.
✅ Encrypt and safely store access/refresh tokens (e.g., secrets manager).
✅ Revoke tokens when no longer needed.
Updated 11 days ago