For developers
The eccuity API
A GraphQL API covering almost everything the eccuity app can do: net worth and portfolio data, trading, funding, and more. Secured with OAuth 2.0, so you, or an app you build, only ever get access to what you explicitly authorize.
Requires a paid eccuity plan (Plus, Pro, or Private).
Overview
The API is available to anyone on a paid eccuity plan who wants to connect an external tool to their account: a personal script, a spreadsheet, an AI assistant, or a full third-party app that other eccuity users could also connect to. Free accounts can authenticate for basic sign-in purposes only, but can't grant a token access to portfolio, trading, or funding data.
Every operation requires a specific scope, and a token can only do what it was explicitly granted, regardless of what your account can otherwise do. GraphQL introspection is disabled in production, so the full reference below, generated from the live schema, is the source of truth for argument and return shapes.
Use cases
A sample of what people build on top of the API, from inside eccuity and out.
Trackers, run by agents
eccuity's own agents platform calls the API to run Trackers (model portfolios) autonomously: rebalancing, contributions and reporting, without anyone clicking through the app.
A custom trading strategy
One of our traders built a standalone trading application on top of the API, running a systematic strategy end to end: reading positions, placing and managing orders.
Your own net worth dashboard
Pull your net worth, holdings and performance into a personal spreadsheet, a Notion database, or a command-line report on your own schedule.
An AI assistant that knows your portfolio
Connect an MCP-compatible AI assistant to your account so it can answer questions about your holdings, or place a trade when you ask it to, within the scopes you grant.
An app your clients connect to
Build a third-party app, budgeting tool, or portfolio tracker that other eccuity users log into and authorize with their own account, via the authorization code + PKCE flow.
Adviser tooling across a book of clients
A lightweight dashboard pulling net worth and portfolio data across every client account your practice has been authorized to see.
Quick start
The fastest way to make your first authenticated call, no redirect flow, no consent screen, is a server-to-server (client credentials) credential. Right for a personal script or backend service acting on your own account.
- 1.Log into the eccuity app and go to Settings → API Credentials.
- 2.Click New credential. Give it a label. Leave “Public client (PKCE)” unchecked.
- 3.Copy the Client ID and Client Secret shown. The secret is shown once, and can't be retrieved again.
- 4.Exchange them for an access token. Your exact API base URL is shown on the Settings → API Credentials page.bash
curl -X POST https://api.<your-eccuity-domain>/oauth/token \ -u "ecc_id_xxxxxxxx:ecc_sk_xxxxxxxx" \ -d "grant_type=client_credentials" \ -d "scope=full_access"Response:
json{ "access_token": "ecc_at_xxxxxxxxxxxxxxxx", "token_type": "Bearer", "expires_in": 3600, "scope": "identity profile.read profile.write portfolio.read portfolio.write trade funding.read funding.write" } - 5. Call the API with the token, a Bearer token good for one hour.bash
curl -X POST https://api.<your-eccuity-domain>/graphql \ -H "Authorization: Bearer ecc_at_xxxxxxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{"query": "query { fetchCurrentUser { id name email } }"}'
Authentication
eccuity's API is a standard OAuth 2.0 authorization server, with two flows depending on what you're building. Both end the same way: a Bearer access token sent as Authorization: Bearer <token> on every request.
Client credentials
A personal script or backend acting on your own account. Confidential credential (client ID + secret), no user interaction: trade the secret for a token directly. Created via Settings → API Credentials.
Authorization code + PKCE
A third-party app that other eccuity users will also log into and authorize. Public credential (client ID only), the user logs in and clicks Allow on a consent screen. Created via Settings → API Credentials (checking “Public client (PKCE)”), or self-registered via /oauth/register.
Option A: client credentials (server-to-server)
If you omit scope, your token only gets the identity scope. Always pass the scopes (or the full_access shortcut) you actually need. A confidential credential's token acts on your own account: there's no separate resource-owner step.
POST /oauth/token
Authorization: Basic base64(client_id:client_secret)
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials&scope=full_accessOption B: authorization code + PKCE (OAuth apps)
Use this if other eccuity users will connect your app to their own account, the standard third-party-app flow.
- 1. Register a client: create a public credential yourself (Settings → API Credentials, with your redirect URI), or self-register via
POST /oauth/register(RFC 7591 Dynamic Client Registration, the zero-touch path used by MCP-style clients). This only issues public (PKCE) clients, and your redirect URI domain must be pre-approved by eccuity (localhost/loopback is allowed automatically outside production, for local development). - 2. Send the user to authorize. PKCE is mandatory: generate a
code_verifier, derive acode_challenge(S256), and hold onto the verifier.GET /oauth/authorize ?response_type=code &client_id=ecc_id_xxxxxxxx &redirect_uri=https://yourapp.com/callback &scope=portfolio.read+trade &state=<random-string> &code_challenge=<S256-challenge> &code_challenge_method=S256A free-plan user can only ever grant
identity: broader scopes requested from a free user are silently dropped down to what their plan allows, and the consent screen explains what was dropped. - 3. Handle the redirect back to your
redirect_uriwith?code=...&state=...(or?error=access_deniedif declined). - 4. Exchange the code for a token.
POST /oauth/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code &code=<the code> &redirect_uri=https://yourapp.com/callback &client_id=ecc_id_xxxxxxxx &code_verifier=<the original verifier>
Redirect URIs must be https:// (plain http:// is only accepted for localhost/127.0.0.1, for local development) and can't contain a fragment.
Token lifetime
Access tokens last one hour and cannot be refreshed: there is no refresh_token grant. Get a new one when a token expires (trivial for client-credentials; a fresh consent round-trip for authorization-code apps). Deleting a credential in Settings immediately revokes every token and unredeemed authorization code issued from it.
Discovery endpoints
Standard OAuth/OIDC discovery is supported, so most OAuth libraries and MCP clients can configure themselves automatically.
GET /.well-known/oauth-authorization-server(RFC 8414)GET /.well-known/oauth-protected-resource(RFC 9728)
Scopes
Every operation requires a specific scope. Your token can only call an operation if it was granted that scope, or a composite scope that includes it: a token can do no more than you can do yourself, but you can hand out a token that does less. Space-separate multiple scopes (e.g. scope=portfolio.read trade). Unknown scope names are rejected outright.
| Scope | Grants |
|---|---|
| identity | Minimal profile info, enough for “Sign in with eccuity” (fetchCurrentUser). |
| profile.read | Read KYC status, investor preferences, AI assistant threads, passkeys, device tokens, appointments, and more. |
| profile.write | Update account and profile settings, KYC submissions, MFA, passkeys, AI assistant threads, broker credentials. |
| portfolio.read | Read holdings, balances, performance, net worth data, strategies, trade ideas. |
| portfolio.write | Non-trading portfolio changes: net worth planning (income, expenses, goals, liabilities), custom instruments, tracked portfolios, share links. |
| trade | Place, edit, and cancel orders; manage strategies. One scope, not split per action. |
| funding.read | Read deposits, withdrawals, cards, bank details, crypto wallets, billing and subscriptions. |
| funding.write | Deposits, withdrawals, saved payment and bank details, cards, crypto wallets, billing. |
| public | No scope beyond a valid token needed: asset lookup, market hours, news, option chains, historical prices, strategy browsing, and account registration/recovery. A handful, like register, don't even require a token. |
Composite (shortcut) scopes
| Scope | Expands to |
|---|---|
| full_access | Every leaf scope above except admin. |
| profile | profile.read + profile.write |
| portfolio | portfolio.read + portfolio.write |
| funding | funding.read + funding.write |
A few operations exist only for the eccuity app itself and are never reachable with a Bearer token, regardless of scope: managing your own API credentials, and the OAuth consent screen's own internal fields. These return a FORBIDDEN error naming the field.
Full reference
Every operation, argument, and return type, generated straight from the live schema so it never drifts from what the API actually accepts.
eccuity API Reference
Full argument names and types, nested return shapes, and a live example query, variables, and response for every query, mutation, and subscription.
A handful of operations are subscriptions: long-lived, pushed updates (live prices, news, export job status, AI assistant streaming) over a WebSocket connection rather than a plain POST. Use a GraphQL client that supports graphql-ws (e.g. Apollo Client, urql) and authenticate the socket with your Bearer token.
Example requests
Read your net worth dashboard (portfolio.read)
query {
netWorthDashboard {
totals {
netWorth
totalAssets
totalLiabilities
totalMonthlyExpenses
totalAnnualIncome
}
assets { id }
liabilities { id }
goals { id }
incomes { id }
expenses { id }
}
}Place a market order (trade)
mutation {
placeOrder(params: {
assetId: "..."
brokerCredentialId: "..."
side: BUY
type: MARKET
quantity: 10
notional: 0
limitPrice: 0
stopPrice: 0
premarket: false
timeInForce: DAY
}) {
id
status
quantityFilled
averageFilledPrice
}
}MARKET orders still require limitPrice/stopPrice/notional in the input shape: pass 0 for whichever don't apply to your order type.
Check your current positions (portfolio.read)
query {
getPositions(params: { tradingMode: "LIVE" }) {
symbol
quantity
averageEntryPrice
marketValue
unrealizedProfitLoss
}
}Errors
Errors follow the standard GraphQL error shape:
{
"errors": [
{
"message": "This token is missing the required scope \"trade\" for: placeOrder",
"extensions": { "code": "FORBIDDEN" }
}
]
}| Situation | What you'll see |
|---|---|
| Missing or expired token | A 401-equivalent GraphQL error, or the field resolves as unauthenticated. |
| Token lacks the required scope | FORBIDDEN, naming the field and the missing scope. |
| Calling a session-only field with a token | FORBIDDEN: “This operation is not available to API tokens.” |
| Your account lacks the underlying permission (e.g. not a paid plan, not KYC'd) | A descriptive validation error from the resolver itself. |
| Bad input | A standard GraphQL validation error before your query even executes. |
Rate limits
- 50 requests/second per authenticated user (or per IP if unauthenticated) across the API generally.
- 5 requests/second on sensitive endpoints (login, token issuance, password reset, MFA): /oauth/token falls under this stricter limit.
Rate-limit headers (Rate-Limit-Remaining, Rate-Limit-Reset, Rate-Limit-Total) are returned on every response so you can back off proactively.
Good practices
- Request only the scopes you need. A token is a bearer credential: anyone who obtains it can act as you, within its scopes. Don't request full_access for a script that only reads net worth data.
- Treat the client secret like a password. It's shown once at creation time and never retrievable again. If you lose it, delete the credential and create a new one.
- Expect 1-hour tokens. Build your integration to request a fresh token when a call fails with an auth error, rather than assuming long-lived access.
- Delete unused credentials. Each one is a live door into your account; Settings → API Credentials shows last-used time and IP so you can spot anything that shouldn't still be active.
- One credential per integration. Makes it easy to revoke a single integration without breaking everything else.
Credentials live in the app. This page is the reference.
eccuity can't issue API credentials from this site. Create and manage yours from inside your account.