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

ParameterRequiredDescription
client_idyesID provided when your app is registered
redirect_uriyesURL where the user is redirected after approval
response_typeyesMust be code
scopeyesSpace-separated list of scopes requested
statenoUnique 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

ParameterRequiredDescription
client_idyesYour Client ID
client_secretyesYour Client Secret
codeyesAuthorization code received in Step 2
redirect_uriyesMust match the one used in Step 2
grant_typeyesMust 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

ParameterRequiredDescription
client_idyesYour Client ID
client_secretyesYour Client Secret
refresh_tokenyesRefresh token received with the access token
grant_typeyesMust 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

ParameterRequiredDescription
client_idyesYour Client ID
client_secretyesYour Client Secret
tokenyesThe 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.