AdviceCloud Firm API
The Firm domain API, first release: the firm's investment configuration — its risk profiles and their asset-class and security-category allocation matrices, its security asset-category overrides, and its minimum trade sizes. This document describes the operations this release publishes and no others.
Base URL
Send every request to the production base URL:
https://app.advicecloudft.com/api
Each path in this document carries the /v1 segment, and it goes after the
base URL. The path /v1/firm/risk is called as
https://app.advicecloudft.com/api/v1/firm/risk.
Versions
The version is a segment of the path. Every operation of this release is
served under /v1. The segment was part of the path from the first
publication, so a caller can pin a version from its first request. The
same path without the segment does not answer.
Each domain API has its own version and is deprecated on its own schedule. A caller that uses three domain APIs tracks three version numbers.
A change that would break a caller that conforms to the published description of a version is made only under a different version of that domain API. Five kinds of change are breaking:
- removing a field
- renaming a field
- narrowing a type
- changing a status code
- changing the meaning of an existing parameter
Two kinds of change are not breaking, and can be made under the same version: adding an optional field, and adding an optional parameter.
Authentication
A script calls the API on behalf of a person, with the OAuth 2.0 device authorization grant. The token belongs to the person, and each call carries the firm, the role and the scopes of that person.
- The script sends a device authorization request to the issuer host. The
request names the
client_id, the script audience and the scopes, for exampleoffline_access read:firm. The response carries adevice_code, auser_codeand averification_uri. - The script shows the code and the address to the person. The person opens the address on any device that has a browser and signs in with their AdviceCloud login.
- The consent page names "AdviceCloud API" and lists the scopes. Approve only a code that you started yourself.
- The script polls the token endpoint on the issuer host with the grant
type
urn:ietf:params:oauth:grant-type:device_code. When the person approves, the script gets an access token, valid for 3600 seconds, and a refresh token. A request for any audience other than the script audience is refused. - The script calls a
/v1path with the access token as a bearer token. The token reaches only operations that require a scope, and only when it carries that scope. The API refuses it with status 403 on every other route that requires a token. - When the access token expires, the script uses the refresh token to get a new one. Each use of the refresh token returns a new refresh token. The person does nothing.
Rules for the script.
- Save the new refresh token before you use the new access token.
- Use one approval in each process. Two processes that share one token file end the approval.
- Do not send a refresh token that was already used. The approval then ends. A retry after a network error can send a used refresh token.
When the person approves again. The refresh fails, and the person does step 2 again, in each of these cases:
- after 30 days with no refresh
- after 90 days in total
- after the script sends a refresh token that was already used
- after AdviceCloud revokes the approval
The values in the example. $ISSUER_HOST, $CLIENT_ID and
$SCRIPT_AUDIENCE are the issuer host, the client_id of the
"AdviceCloud API" client and the identifier of the script audience. The
security scheme of this document states whether they are published yet.
Use the issuer host exactly as published: a token from a different host
gets status 401 from the API. $API_ROOT is the base URL above.
# 1. Start. Show verification_uri_complete, or verification_uri and user_code, to the person.
curl -s -X POST "https://$ISSUER_HOST/oauth/device/code" \
-d client_id="$CLIENT_ID" -d audience="$SCRIPT_AUDIENCE" \
-d scope="offline_access read:firm"
# 2. Poll every `interval` seconds until the person approves.
# Save refresh_token before you use access_token.
curl -s -X POST "https://$ISSUER_HOST/oauth/token" \
-d grant_type="urn:ietf:params:oauth:grant-type:device_code" \
-d device_code="$DEVICE_CODE" -d client_id="$CLIENT_ID"
# 3. Call the API.
curl -s "$API_ROOT/v1/firm/risk" -H "Authorization: Bearer $ACCESS_TOKEN"
# 4. Renew. Save the new refresh_token before you use the new access_token.
# Do not send the old refresh_token again. Auth0 then ends the approval.
curl -s -X POST "https://$ISSUER_HOST/oauth/token" \
-d grant_type=refresh_token -d client_id="$CLIENT_ID" -d refresh_token="$REFRESH_TOKEN"
Scopes
The public API has 12 scopes: a read: and a write:
scope for each of the six domain APIs. All of them are listed here,
although this release publishes only the Firm domain API. In this release,
read:firm authorizes the reads and write:firm authorizes the writes, and
every operation requires one of the two on the bearer token.
Each scope authorizes the route prefixes listed after it:
read:household:/households,/accounts,/contacts,/households/:householdId/rmd,/rmd,/searchwrite:household:/households,/accounts,/contacts,/households/:householdId/rmd,/rmd,/searchread:firm:/firm,/users,/profilewrite:firm:/firm,/users,/profileread:model:/modelswrite:model:/modelsread:billing:/billing,/cash-settingswrite:billing:/billing,/cash-settingsread:report:/reports,/reports/13f,/insights,/dashboard,/activity-logwrite:report:/reports,/reports/13f,/insights,/dashboard,/activity-logread:rebalance:/rebalances,/transactions,/transfers,/securitieswrite:rebalance:/rebalances,/transactions,/transfers,/securities
read:report, write:report, read:rebalance and write:rebalance
authorize more than their names say. The prefixes above are the whole of
what each one covers.
The Client domain API's scopes are read:household and write:household.
In an OAuth scope the word "client" already names the OAuth client, so a
scope built on "client" would read two ways.
Pagination
Each operation that returns a list whose length the request does not bound takes two query parameters:
limit: how many rows the response carries. The default is 50.offset: how many rows to skip before the first row of the response. The default is 0.
No maximum page size applies. The server uses the limit you send; it does
not lower it and does not refuse it for being large.
A malformed limit or offset is refused with status 400, not replaced
with the default: a negative number, a decimal, exponent notation, a value
that is not a number, or a limit below 1.
The response keeps the data key that every response carries, and puts a
pagination key beside it. pagination holds the limit and offset
that produced the page and totalCount, the size of the whole collection:
{
"data": [ ... ],
"pagination": { "limit": 50, "offset": 0, "totalCount": 137 }
}
Rows can be skipped or repeated when the collection changes between requests. A script that walks a collection page by page can then get a wrong answer, with no error and no way to detect it. The API has no cursor today.
Errors
Every error response carries the same JSON body, whatever the operation:
{
"statusCode": 400,
"message": "One sentence that a person can read.",
"details": { ... }
}
statusCode: a number, always present. It equals the HTTP status of the response.message: a string, always present. One sentence that a person can read.details: an object, present only when the operation has facts of its own to report. Read only the keys in it that you understand.
A caller can read statusCode and message without knowing which
operation it called.
What each status means:
400: the request is malformed. A query parameter or the request body fails validation. When more than one check fails,messagejoins their sentences with;.401: the request carries no bearer token, or the API does not accept the token, for example because it has expired or because a different issuer host issued it.403: the API accepts the token, but not for this operation. The role of the person does not permit the operation, or the token does not carry the scope that the operation requires.404: no operation matches the method and the path of the request. What an operation returns for an id that does not exist is stated on that operation.429: the request is over a rate limit. See Rate limit.500: the API failed. The body is{ "statusCode": 500, "message": "Internal server error" }.
A status 500 on the first call after the approval. This status is not a temporary fault. Do not retry the call. The API returns 500 when the user record of the person has no firm, and a retry does not change the user record.
Rate limit
The API has two rate limits, and it counts each request on one of them at most. A request over a limit gets status 429.
The limit for each person. A request whose token is for the script
audience is counted on the limit of the person that the token belongs to:
30 requests per 10 seconds per person per API instance. The person has
one count for all of their requests, whatever the operation. The API
counts the request after it accepts the token and before it checks the
scope, so a request that gets status 403 is counted, and a request that
gets status 401 is not. A request over this limit gets the
Retry-After-per-person header, which gives the number of seconds to wait
before the next request of that person.
The limit for each client IP address. Every other request is counted by
the client IP address: 30 requests per 10 seconds from one client IP address
to one operation. Each operation keeps its own count. Requests sent from
the same address share one count. The request is counted before the token
is checked, so a request that gets status 401 is counted too. A request
whose bearer token names the script audience is not counted on this limit.
A request over this limit gets the Retry-After header, which gives the
number of seconds to wait before the next request to that operation.
Each API instance counts on its own. Each API instance keeps its own counters in its own memory, and the counters are not shared. A caller's requests are spread across however many instances are running, so the limit a caller actually meets is up to the instance count times higher than the figures above, and the caller cannot control or see which instance answers a request. Treat each figure as the floor that is always enforced, not as a quota to spend.
Deprecation
Removing or breaking a published operation is announced before the change ships. The minimum notice period is 90 days, and it runs from the first publication of the version that is deprecated, not from the announcement.
The announcement uses all four of these channels:
- the
Deprecationresponse header, sent on the deprecated operation deprecated: trueon the operation in this document- an entry in the changelog, written for the release that announces the deprecation
- a direct email to each customer of the domain API that is deprecated
The Deprecation response header is not built yet. Nothing in version 1 is
deprecated, so no operation sends it. A future deprecation will use it.
Changes and revisions
The changelog lists the changes to this document. One changelog covers every domain API, and each entry names the domain API and the version it belongs to, because each domain API has its own version.
- For a person:
https://app.advicecloudft.com/api/openapi/changelog - For a script:
https://app.advicecloudft.com/api/openapi/changelog.json
An additive change is made under the same version, so what this document
says about a version can change while that version is served. Each
revision of this document is identified by the date it was first
published, in the form YYYY-MM-DD, with a -N suffix only when a second
revision was published on the same day.
This document is revision 2026-09-24-2. It is at:
https://app.advicecloudft.com/api/openapi/revisions/2026-09-24-2.json
An earlier revision is at the same address with its own identifier in place of this one. A revision stays available while any domain API version it describes is served. The changelog lists the revisions that are kept.
Authentication
- OAuth 2.0: scriptAccess
OAuth 2.0 device authorization grant (RFC 8628). A script calls the API on behalf of a person. The script shows a short user code and a verification address. The person opens that address on any device that has a browser, signs in, and approves a code that they started themselves. The token belongs to the person, and each call carries the firm, the role and the scopes of that person.
Grant types: urn:ietf:params:oauth:grant-type:device_code and
refresh_token, and no others. OpenAPI 3.0 and 3.1 have no flow type
for the device authorization grant, so this scheme names no standard
flow. The grant types are in x-grant-types, and the scopes are in the
scopes map of the x-deviceCode flow.
An access token is valid for 3600 seconds. Each use of a refresh token returns a new refresh token. A refresh token expires after 2592000 seconds (30 days) with no use, and after 7776000 seconds (90 days) in total. A person cannot change these lifetimes. Offline access is allowed, which is what issues the refresh token.
These values are not yet published: the issuer host, the device authorization address, the token address, the client id of "AdviceCloud API" and the identifier of the script audience. This document gives no placeholder in their place.
x-audiences lists the audiences that this API accepts in the aud
claim of a token. The API refuses a token whose aud is none of them.
This scheme does not describe the audience of the MCP server, which uses
a different audience.
Security Scheme Type: | oauth2 |
|---|---|
OAuth Flow (x-deviceCode): | Scopes:
|