Managed Accounts¶
A managed account is the custody account Paros operates for you. It holds the assets, it
carries the address that venues see, and every strategy and playlist you create points at
its id.
What is on an account¶
| Field | Meaning |
|---|---|
id |
The identifier every strategy, playlist and position call takes |
name |
Your label for the account |
address |
The on-chain address. For Hyperliquid this becomes the master address |
customerId |
The owning customer |
agentId |
The trading agent bound to the account |
status |
Lifecycle state: active, inactive or paused |
orchestrationContractAddress |
The orchestration contract, where the account has one |
Create an account¶
POST /api/managed-accounts
{ "saltAccountId": "<salt account uuid>" }
saltAccountId is required. The managed account is built from an existing Salt custody
account, so the optional X-Salt-Token header applies to this call.
Open the diagram full screen ↗
Worked example: a 3-of-5 account¶
The vault comes first. Five signers, three required to move funds, created on the Salt side. Paros never creates it and never counts signatures; it binds the finished vault to a managed account.
{
"signers": ["0xA1...", "0xB2...", "0xC3...", "0xD4...", "0xE5..."],
"threshold": 3
}
Custody returns the vault id once the signer set is final. That id is the only thing the Paros call needs:
POST /api/managed-accounts
X-Salt-Token: <session token>
{ "saltAccountId": "3f7c1e9a-..." }
The answer arrives before the account is usable:
{ "jobId": "b41d...", "saltAccountId": "3f7c1e9a-...", "saltOrgId": "...", "status": "pending" }
Poll until the status settles:
GET /api/managed-accounts/{id}
{ "id": "...", "status": "active", "address": "0x...", "saltSigners": ["0xA1...", "0xB2...", "0xC3...", "0xD4...", "0xE5..."] }
saltSigners comes back with all five. The threshold is not stored by Paros, so read it from
the vault when you need it. A 409 here means that vault is already bound, and a vault whose
organisation differs from the customer's is refused outright.
This returns 202, not 201
Creation is asynchronous. The response acknowledges the request and the account becomes
usable once the custody side finishes. Poll GET /api/managed-accounts/{id} until
status settles before you point a strategy at the account.
A 409 means that custody account is already bound to a managed account. Read the existing
one instead of sending the create again.
A new account starts with no policies of its own. It inherits whatever the customer has already set, which is covered under Policies on an account.
List and read¶
GET /api/managed-accounts
GET /api/managed-accounts/{id}
The list takes optional filters: page, limit, status, customerId, saltOrgId,
agentId, search, sortBy, sortOrder.
Filter by customerId when you know the owner, and by agentId when you are tracing which
account a trading agent sits on.
A 404 on the single read means the id does not exist or is not visible to the caller.
Update an account¶
PUT /api/managed-accounts/{id}
{
"name": "Northwind main",
"agentId": "<uuid>",
"status": "active",
"orchestrationContractAddress": "0x...",
"stepInTriggerCategory": "manual",
"stepInReason": "operator pause"
}
Every field is optional, so send only what changes.
agentId and orchestrationContractAddress are signer-relevant, which means the optional
X-Salt-Token header applies to those updates. A 409 signals a conflict, most often an
address that another account already uses.
Policies on an account¶
Policies decide what an account is allowed to do. Each one is a row with a type and a
config, and it either blocks execution or records a preference. The account-level ones sit
here; the customer-level ones are described under
Customers, and both apply at once.
GET /api/managed-accounts/{accountId}/policies
GET /api/managed-accounts/{accountId}/policies/enforced
GET /api/managed-accounts/{accountId}/policies/effective-config
POST /api/policies
PUT /api/policies/{id}
DELETE /api/policies/{id}
GET .../policies returns the account's own rows. .../enforced returns what is actually in
force, customer policies included. .../effective-config returns the merged config the
execution path reads. When a strategy is refused and you cannot see why, read the enforced
list first.
These endpoints are not in the public reference yet
The policy routes are tagged internal in the API spec, so they do not appear under
/api-docs. Until they do, the calls above are the contract.
The types that block execution¶
| Type | Config | What it does |
|---|---|---|
whitelist |
sendWhitelistOnly, whitelists[] |
Restricts transfers to listed addresses |
blacklist |
addresses[] |
Refuses listed addresses |
limit |
enabled, maxTokenAmt, maxUsdAmt |
Caps each transaction by token and by USD |
venue |
allowedVenues[] |
Hard allowlist of venue slugs |
exposure |
— | Reserved. The shape is a placeholder, so no exposure limit is enforced today |
hyperliquid |
spotReserveFloorUsd |
Spot USDC kept back from transfers, withdrawals and strategies |
routing (dexOrder[]) and gas (alertBelowThreshold, gasManagement) are the two
preference types. They steer behaviour, and they never block a transaction.
What creates a default¶
Nothing. Creating a managed account writes no policy rows, so a new account is unrestricted at this layer until you add something. It still inherits the customer's policies, and that is usually where an account gets its first constraint.
sendWhitelistOnly defaults to true when you create a whitelist policy, so a whitelist
takes effect at the moment it exists unless you explicitly turn the switch off.
Open the diagram full screen ↗
How the two scopes combine¶
Customer policies cover every account. Account policies apply on top, and the rules differ by type:
- Whitelist. The account's addresses must be a subset of the customer's. An account can narrow the customer list; it cannot widen it.
- Blacklist. Entries are unioned. An address blocked at either level is blocked.
- Everything else. The nearest row wins, and an account row replaces the customer's for that type.
Who may write¶
Only a customer user (owner or member) can create a policy. Paros staff cannot write one
on your behalf. A policy attached to an account also requires the caller to be a signer on
that account, and a customer-wide policy requires owner authority.
Retire an account¶
Retiring is a guarded transition. The account moves to inactive and stays on record.
Check what would block deactivation¶
GET /api/managed-accounts/{id}/deactivation-check
The pre-flight read reports blockers, warnings and toArchive, the last being how many
draft and paused strategies would be archived. Deactivation is rejected while blockers
remain: active strategies, running executions, open Hyperliquid orders or TWAPs.
Deactivate¶
POST /api/managed-accounts/{id}/deactivate
No body. Deactivation archives your draft and paused strategies, revokes the account's
Hyperliquid agent and sets the account to inactive. An inactive account cannot run or
create strategies, bind venues, or receive transfers. A 409 means a blocker is still there.
An inactive account is still on record, still readable, and its history is intact. Count it as the end state: there is no step after this one.
Custody boundary¶
Creating an account and updating its signer-relevant fields both pass through the custody layer. The failures below come from that boundary, not from the account resource:
| Status | What happened |
|---|---|
403 |
Signing policy refused the call. If your organisation requires co-sign, a call without a valid X-Salt-Token lands here |
419 |
The Salt token expired. Re-authenticate and retry |
502 / 504 |
The custody service or a downstream RPC did not respond |
After a 502 or 504, read the account before resending a create or an update.