OAuth

OAuth

OAuth 2.0 enables third-party applications to access Addepar data on behalf of users without handling their credentials directly. If your application serves multiple users or requires delegated access, OAuth is the required authentication method. Failing to implement it correctly results in 401 or 403 responses on every API call.

How it works

The Addepar OAuth implementation uses the Authorization Code grant flow:

  1. Your application redirects the user to Addepar's authorization URL.
  2. The user logs in to Addepar and selects their firm (if they have access to multiple firms).
  3. The user authorizes the requested scopes.
  4. Addepar redirects back to your redirect_uri with a one-time authorization code appended.
  5. Your application exchanges the authorization code (plus your client secret) for an access token and refresh token.
  6. Your application uses the access token in API requests until it expires (240 seconds), then uses the refresh token to obtain a new access token.

Client setup

Before you can authenticate users, you need credentials from Addepar and must provide redirect configuration.

Addepar provides:

CredentialDescription
Client IDA string identifying your application (e.g., your firm name)
Client SecretA 256-bit hex-encoded secret for token exchange
Authorized ScopesThe set of scopes your application can request

You provide to Addepar:

RequirementDescription
Redirect URIThe URL Addepar redirects to after authorization; multiple URIs supported
Terms of Service URLYour application's terms
Privacy Policy URLYour application's privacy policy

Parameters and headers

Authorization request

ParameterLocationTypeRequiredConstraintsDescription
response_typeQuerystringYesMust be codeSpecifies the grant type
client_idQuerystringYesProvided by AddeparIdentifies your application
redirect_uriQuerystringYesMust be URL-encoded; must match a registered redirect URIWhere Addepar sends the authorization code
scopeQuerystringYesSpace-separated list from available scopesThe permissions your application requests
stateQuerystringRecommendedOpaque string; returned unchanged in the redirectPrevents CSRF attacks
code_challengeQuerystringNo (required for PKCE)Hex-encoded SHA-256 hash, min 32 charactersThe PKCE challenge derived from your code verifier
code_challenge_methodQuerystringNo (required for PKCE)Must be S256The hash algorithm used for the challenge

Token request

ParameterLocationTypeRequiredConstraintsDescription
client_idBodystringYesProvided by AddeparIdentifies your application
client_secretBodystringYes (confidential clients)256-bit hex stringAuthenticates your application
grant_typeBodystringYesauthorization_code or refresh_tokenThe type of token request
codeBodystringYes (initial exchange)One-time use; expires after 60 secondsThe authorization code from the redirect
redirect_uriBodystringYes (initial exchange)Must match the authorization requestThe same redirect URI used in step 1
refresh_tokenBodystringYes (refresh flow)Obtained from initial token exchangeUsed to obtain a new access token
code_verifierBodystringNo (required for PKCE)The original random string used to derive code_challengeProves possession of the challenge

Token response

FieldTypeDescription
access_tokenstringBearer token for API requests; valid for 240 seconds
refresh_tokenstringToken for obtaining new access tokens; store securely
addepar_subdomainstringThe firm's subdomain for API calls
addepar_firmstringThe firm ID for the Addepar-Firm header
token_typestringAlways bearer
expires_inintegerToken lifetime in seconds (240)

Integration example

Step 1: Request authorization

Redirect the user to the authorization URL. Your Addepar contact provides the correct base URL for your integration.

# Construct the authorization URL (example)
https://id.addepar.com/oauth2/authorize?\
  response_type=code&\
  client_id=your_client_id&\
  redirect_uri=https%3A%2F%2Fyour-app.com%2Fcallback&\
  scope=portfolio&\
  state=random_csrf_token

After the user authorizes, Addepar redirects to your URI with the code:

https://your-app.com/callback?code=EXAMPLE_AUTH_CODE_4f8a2b91c6d3e507&state=random_csrf_token

Step 2: Exchange the code for tokens

curl --request POST \
  --url 'https://examplefirm.addepar.com/api/public/oauth2/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=your_client_id' \
  --data-urlencode 'client_secret=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd' \
  --data-urlencode 'grant_type=authorization_code' \
  --data-urlencode 'redirect_uri=https://your-app.com/callback' \
  --data-urlencode 'code=EXAMPLE_AUTH_CODE_4f8a2b91c6d3e507'
{
  "access_token": "EXAMPLE_ACCESS_TOKEN_9f3c7d21",
  "refresh_token": "EXAMPLE_REFRESH_TOKEN_2b8e4a96f1c05d3a",
  "addepar_subdomain": "examplefirm",
  "addepar_firm": "2",
  "token_type": "bearer",
  "expires_in": 240
}

Step 3: Use the access token

curl --request POST \
  --url 'https://examplefirm.addepar.com/api/v1/portfolio/query' \
  --header 'Authorization: Bearer EXAMPLE_ACCESS_TOKEN_9f3c7d21' \
  --header 'Addepar-Firm: 2' \
  --header 'Content-Type: application/vnd.api+json' \
  --data '{
    "data": {
      "type": "portfolio_query",
      "attributes": {
        "columns": ["value"],
        "groupings": [],
        "start_date": "2026-01-01",
        "end_date": "2026-07-01",
        "portfolio_type": "FIRM",
        "portfolio_id": 1
      }
    }
  }'

Step 4: Refresh an expired token

Access tokens expire after 240 seconds. Use the refresh token to obtain a new one without re-prompting the user.

curl --request POST \
  --url 'https://examplefirm.addepar.com/api/public/oauth2/token' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'client_id=your_client_id' \
  --data-urlencode 'client_secret=0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcd' \
  --data-urlencode 'grant_type=refresh_token' \
  --data-urlencode 'refresh_token=EXAMPLE_REFRESH_TOKEN_2b8e4a96f1c05d3a'
{
  "access_token": "EXAMPLE_ACCESS_TOKEN_7a1d9c44",
  "refresh_token": "EXAMPLE_REFRESH_TOKEN_2b8e4a96f1c05d3a",
  "addepar_subdomain": "examplefirm",
  "addepar_firm": "2",
  "token_type": "bearer",
  "expires_in": 240
}

PKCE (public clients)

Proof Key for Code Exchange adds a security layer for applications that cannot store a client secret (single-page apps, mobile apps). Addepar's implementation uses hexadecimal encoding for the challenge, which differs from the RFC 7636 standard (base64url).

Generate the verifier and challenge

import hashlib, os

code_verifier = os.urandom(32).hex()  # 64-char random hex string
code_challenge = hashlib.sha256(code_verifier.encode()).hexdigest()

Include in the authorization request

Add code_challenge and code_challenge_method=S256 to your authorization URL query parameters.

Include in the token exchange

Add code_verifier to your token request body. The server verifies that SHA256(code_verifier) matches the stored challenge before issuing tokens.

Available scopes

ScopeAccess levelDescription
PROFILERead-onlyName, email, user ID, and firm ID
PORTFOLIORead-onlyPortfolio data including name, quantity, and value for all clients, entities, accounts, and securities
TRANSACTIONS / TRANSACTIONS_WRITERead or read-writeTransaction data including type, owner, and value
FILES / FILES_WRITERead or read-writeFile names and content associated with clients
GROUPS / GROUPS_WRITERead or read-writeGroup details, attributes, and membership
ENTITIES / ENTITIES_WRITERead or read-writeEntities including clients, trusts, accounts, and investments
POSITIONS / POSITIONS_WRITERead or read-writeOwnership positions between entities
USERS / USERS_WRITERead or read-writeUser details, contacts, and affiliations
TEAMS / TEAMS_WRITERead or read-writeTeams and team membership
AUDIT_TRAILRead-onlyAudit logs including transactions, reports, roles, and permissions
REPORTS_WRITEWriteReport generation (no read-only scope available)
BENCHMARKS_READ / BENCHMARKS_WRITERead or read-writeBenchmarks, associations, compositions, and imported data
BILLING_READ / BILLING_WRITERead or read-writeBillable portfolios and fee schedule assignments

Error recovery

FailureResponseRecovery
Authorization code expired400 Bad Request on token exchangeThe code expires after 60 seconds. Restart the authorization flow from step 1.
Invalid client secret400 Bad Request on token exchangeVerify the client secret matches what Addepar provided. Secrets are case-sensitive.
Access token expired401 Unauthorized on any API callUse the refresh token to obtain a new access token. Do not re-prompt the user.
Refresh token invalid400 Bad Request on refreshThe refresh token may have been revoked. Restart the full authorization flow.
Scope not authorized403 Forbidden on API callThe user did not grant the scope your request requires. Re-authorize with the correct scope.
PKCE challenge mismatch400 Bad Request on token exchangeVerify you are using hex encoding (not base64url) and that the verifier matches the challenge sent in the authorization request.
Redirect URI mismatch400 Bad Request on authorizationThe redirect URI must exactly match one of the URIs registered with Addepar, including protocol and path.

Related resources

  • Get Set Up -- Initial API access and credential generation
  • Rate Limiting -- Request limits that apply to all authenticated calls
  • Response Codes -- Complete reference for HTTP status codes

What’s Next

Did this page help you?