The Investment API uses opaque access tokens obtained using the OAuth 2.0 Client Credentials flow.
During the onboarding process, each client receives a client ID, a client secret, and a key ID which are required to request access tokens via our OAuth 2.0 endpoints.
All endpoints in the Upvest API are protected through authentication. This means you must authenticate before you call the API.
Authentication is a three-step process:
- Request an access token from the token endpoint, specifying the scopes you need. Access tokens expire within 1800 seconds (30 mins). When this occurs, you must request a new token.
- Read the token response to extract the access token and its metadata.
- Call the API with that token in the
Authorizationheader until it expires, then request a new one.
Every request to Upvest, including the request to the token endpoint, must additionally be signed using HTTP message signatures. The client credentials identify and authorise you; the message signature protects the request against tampering in transit. Make sure you have set up request signing before you attempt to fetch a token.
Send a POST request to the token endpoint for your environment:
| Environment | Token endpoint |
|---|---|
| Sandbox | https://sandbox.upvest.co/auth/token |
| Live | https://api.upvest.co/auth/token |
The request body must be application/x-www-form-urlencoded and contain the following fields:
| Field | Required | Description |
|---|---|---|
client_id | Yes | The client ID provided during onboarding. |
client_secret | Yes | The client secret provided during onboarding. |
grant_type | Yes | Must always be client_credentials. |
scope | Yes | A space-delimited list of the scopes you are requesting for this token. See Authentication scopes. |
The request must also carry the upvest-client-id header and the HTTP message signature headers (signature and signature-input).
curl --request POST 'https://sandbox.upvest.co/auth/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'upvest-client-id: <your_client_id>' \
--header 'signature: <generated_signature>' \
--header 'signature-input: <generated_signature_input>' \
--data-urlencode 'client_id=<your_client_id>' \
--data-urlencode 'client_secret=<your_client_secret>' \
--data-urlencode 'grant_type=client_credentials' \
--data-urlencode 'scope=users:read users:admin'See the HTTP message signatures page for how to generate the signature and signature-input headers.
All tokens in both the sandbox and the live environments expire within 1800 seconds (30 minutes). When the token expires, you must request a new token.
A successful request returns a JSON body containing the token and its metadata:
{
"access_token": "<access_token>",
"expires_in": 86400,
"token_type": "bearer",
"scope": "users:read users:admin"
}| Field | Description |
|---|---|
access_token | The access token to send on subsequent API requests. |
expires_in | The token's lifetime in seconds (in this example, 86400 seconds / 24 hours). |
token_type | Always set equal to bearer. |
scope | The scopes granted to this token. |
Send the token in the standard Authorization header on every API request, and continue to send upvest-client-id and your message-signature headers:
upvest-client-id: <your_client_id>
Authorization: Bearer <access_token>
signature: <generated_signature>
signature-input: <generated_signature_input>The token remains valid for the number of seconds returned in expires_in. Reuse the same token across requests until it is close to expiry, then request a new one. Avoid fetching a new token for every call.
- Scope tokens to the task. Request only the scopes a given workflow needs. For example, use a
users:readtoken for read-only access, and request a separateusers:admintoken only when you need to onboard or modify users. - Cache and reuse tokens. Hold a token until shortly before
expires_inelapses rather than requesting one per call, to reduce latency and load. - Store your client secret securely. Treat the client secret like a password: keep it server-side, never embed it in client applications or commit it to source control.
The Upvest Investment API supports granular authentication scopes to improve security. By specifying scopes when you request a token, you restrict the permissions and data access that token grants.
Two distinct sets of scopes are relevant:
- Scopes granted to your client — the full set of scopes Upvest has enabled for you. Upvest provides this list when sending your API credentials during onboarding. You can only request scopes from this set.
- Scopes on a token — the subset of your granted scopes that you request in the
scopefield when fetching a particular token. A token only carries the permissions you explicitly request for it.
You must ensure that each token carries the scopes required by the endpoints you call with it. The required scopes for each endpoint are listed in the security details of that endpoint.
In the API reference: open the endpoint to view the required scopes. You can select View security details in the Security section to see scope details.

The Security window shows additional details, including optional scopes for the endpoint.

New functionality may introduce new scopes, and the set of scopes can change over time. The security details in the API reference are always the authoritative, up-to-date source for which scopes an endpoint requires. The table below is a convenience snapshot and may lag behind the API reference.
The following scopes are available in the Upvest Investment API. For the current required scopes of any specific endpoint, always check its security details in the API reference.
| Scope | Permissions |
|---|---|
| Users, accounts, and access | |
accounts:read | Read accounts and account groups. |
accounts:admin | Create, update, and delete accounts and account groups. |
users:read | Read users and user identifiers. |
users:admin | Create, update, and delete users and user identifiers. |
businesses:read | Read business entities. |
businesses:admin | Create, update, and delete business entities. |
checks:read | Read checks by user or by check ID. |
checks:admin | Create checks. |
roles:read | Read roles. |
roles:admin | Create, update, and deactivate roles. |
| Taxes | |
taxes:read | Read tax residencies. |
taxes:admin | Modify tax residencies and create or modify tax exemptions. |
| Instruments and prices | |
instruments:read | Read instruments. |
prices:read | Read instrument prices, including latest price and open, high, low, close (OHLC) prices. |
| Orders | |
orders:read | Read orders. |
orders:admin | Create, update, and delete orders. |
| Investment solutions | |
portfolios:read | Read portfolios. |
portfolios:admin | Modify portfolios, including allocations, configurations, and rebalancing. |
savings_plans:read | Read savings plans. |
savings_plans:admin | Create and read savings plans. |
account_liquidations:read | Read account liquidations. |
account_liquidations:admin | Trigger, read, and cancel account liquidations. |
| Payments | |
payments:read | Read pay-in and withdrawal operations. |
payments:admin | Pay-in and withdrawal operations, including direct debits and withdrawals. |
mandates:read | Read mandates. |
mandates:admin | Create, update, and delete mandates. |
reference_accounts:read | Read reference accounts. |
reference_accounts:admin | Create, update, and delete reference accounts. |
topups:read | Read top-up operations. |
topups:admin | Top-up operations. |
credit_fundings:read | Read credit funding details. |
| Balances | |
positions:read | Read positions and cash balances. |
valuations:read | Read account valuations. |
account_returns:read | Read account returns. |
virtual_cash_balances:admin | Increase and decrease virtual cash balances. |
| Fees | |
fees:read | Read fee operations. |
fees:admin | Create and read fee operations. |
transaction_fees:read | Read transaction fee operations. |
transaction_fees:admin | Create and read transaction fee operations. |
| Reporting | |
files:read | Read file metadata. |
reports:read | Read reports. |
reports:admin | Create reports. |
transactions:read | Read cash and securities transactions. |
| Transfers | |
securities_transfers:read | Read securities transfers. |
securities_transfers:admin | Create and read securities transfers. |
account_transfers:read | Read account transfers. |
account_transfers:admin | Account transfer operations. |
isa_transfers:admin | ISA transfer operations. |
| Corporate actions | |
corporate_actions:admin | Create, update, and delete corporate action instructions. |
| Webhooks | |
webhooks:read | Read webhooks. |
webhooks:admin | Create, update, and delete webhooks. |
| Tests | |
tests:admin | Trigger a bank transaction for testing purposes. |
For more on how scopes map to permissions, see Permissions.
Scope errors include a status code to help you troubleshoot.
If you request a scope that has not been granted to your client, the token endpoint responds with status 401 and does not issue a token.
This happens when the scope field includes a scope that is outside the set Upvest enabled for you. Because the set of scopes granted to your client is configured on the Upvest side, you cannot change it yourself.
To resolve a 401:
- Compare the scopes in your request against the list of granted scopes Upvest sent you during onboarding, and remove any scope that is not on that list.
- If your integration needs a scope that has not been granted, contact the Upvest team to enable the required scopes.
If you call an endpoint with a token that does not include the scope that endpoint requires, the API responds with status 403.
This happens when the scope is available to your client but was not included in the scope field when you fetched the token. In other words, the permission exists for you, but this particular token does not carry it.
To resolve a 403:
- Check the endpoint's required scopes under View security details in the API reference.
- Request a new token whose
scopefield includes the required scope, then retry the request with that token.
A 401 means the scope is not available to your client and Upvest needs to enable it. A 403 means the scope is available to you but missing from the token you used, which you can simply request a token with the required scopes.
Using client_credentials alone lets us identify and authorise you, but it does not protect against a man-in-the-middle attack, where a request could be maliciously modified in transit.
To mitigate this, Upvest requires an additional layer of public key infrastructure in the form of HTTP message signatures on every request, including the token request.