User & Team Management¶
A customer is the entity Paros onboarded: legal name, contact details, jurisdiction, account type, risk profile.
A member is not a login. It is a directory entry with a role and an access level, so adding one does not give anyone credentials and removing one does not lock anyone out. Sign-in is a separate concern, covered under Authentication.
Every user in Paros is a wallet. A team member is a wallet attached to a customer together
with a role: owner or member. An owner administers the organisation, including who
belongs to it and at what access level. A member works inside the organisation but cannot
change its membership. The role sits on the membership record, keyed to the wallet address,
and appears on the member object as accessLevel.
Customers¶
Creating a customer also creates its custody organisation, so this is the call that turns a wallet into an onboarded entity.
Open the diagram full screen ↗
GET /api/customers
POST /api/customers
GET /api/customers/{id}
PUT /api/customers/{id}
List customers¶
GET /api/customers
Every query parameter is optional: page, limit, status, search, sortBy,
sortOrder, accountType, saltOrgId.
This is not a directory of Paros's customers. It is the list of customers your wallet can reach, which the server decides from three things: customers you created, customers where your wallet is the recorded Salt owner address, and customers where your wallet is an active member.
A wallet can be an active member of more than one customer organisation, so this endpoint can legitimately return several customers for one person. A single-organisation integration will usually see exactly one, but the response is a list and should be treated as one.
The response is paged: items holds the customers and the envelope carries page, limit,
total and totalPages. Each item includes the customer id, entityName, contact details,
status, saltOrgId and saltOwnerAddress.
Create a customer¶
POST /api/customers
{
"entityName": "Northwind Capital LP",
"contactName": "Dana Whitfield",
"contactEmail": "dana@northwind.example",
"contactTelegram": "@dana_w",
"contactPhone": "+44...",
"jurisdiction": "GB",
"yearOfBirth": 1985,
"nationality": "GB",
"timezone": "Europe/London",
"accountType": "institutional",
"saltOrganisation": {},
"riskProfile": {},
"userId": "<uuid>"
}
Four fields are required: entityName, contactName, contactEmail, accountType.
Everything else is optional and can be filled in as onboarding progresses.
The call creates the customer together with its custody organisation, which is why it accepts
an optional X-Salt-Token header. If your organisation's signing policy requires custody
co-signature, a call without a valid token is refused.
A 409 means the customer already exists
Two customers with the same identifying details are not allowed. Do not retry the
POST. Search for the existing record with GET /api/customers?search=... and continue
from there.
Read and update a customer¶
GET /api/customers/{id}
PUT /api/customers/{id}
PUT accepts the same fields as create, all optional, plus status. Send only what
changes; omitted fields keep their current values.
404 means the id does not exist or is not visible to you. 403 mostly means the record is
not yours to modify.
Accounts for a customer¶
GET /api/customers/{id}/managed-accounts?page={n}&limit={n}&status={status}
Returns the custody accounts belonging to one customer. All three parameters are optional. Use this instead of filtering the global account list when you already know which customer you want. The account object itself is described on Managed Accounts.
Policies that cover every account¶
Some policies belong to the customer rather than to one account. Created with a customerId
and no accountId, they apply to every account the customer has today and to any account
added later.
POST /api/policies
PUT /api/policies/{id}
DELETE /api/policies/{id}
{
"customerId": "<uuid>",
"name": "Approved recipients",
"type": "whitelist",
"config": { "sendWhitelistOnly": true, "whitelists": [ { "address": "0x...", "label": "Treasury" } ] },
"enforced": true
}
The types are the same ones an account can carry: whitelist, blacklist, limit, venue,
exposure and hyperliquid block execution, while routing and gas record a preference.
The account page describes each type and the config it takes.
Two rules matter when both scopes are in play. An account's whitelist must be a subset of the customer's, so an account can narrow the list but never extend it. Blacklists are unioned, so an address blocked at either level is blocked.
Only an owner can write a customer-wide policy, which follows from it changing every
account at once. Narrowing for a single account is described under
Managed Accounts.
Members¶
Members are wallets with a role attached to a customer. The four calls below cover the lifecycle: list the directory, add an entry, change it, remove it.
Open the diagram full screen ↗
GET /api/customers/{id}/members?status={status}
POST /api/customers/{id}/members
PUT /api/customers/{id}/members/{memberId}
DELETE /api/customers/{id}/members/{memberId}
The list takes an optional status filter. Everything else is scoped by {id}.
Add a member¶
{
"name": "Robin Achterberg",
"role": "member",
"address": "0x...",
"country": "NL",
"email": "robin@northwind.example",
"telegram": "@robin_a",
"sendEmailNotification": true,
"accessLevel": "member"
}
Only name is required. The optional X-Salt-Token header applies here for the same reason
as customer creation: a directory write that can place a signer touches the custody layer.
Update a member¶
PUT takes the same fields, all optional, plus status.
Remove a member¶
DELETE needs only the two ids. A 409 means the member cannot be removed in their current
state, most often because a pending signature still references them. Clear that work first,
then retry the delete.
The same 409 means two different things on members
On PUT it means an identity field is already taken, usually a duplicate address or
email. On DELETE it means the member is still referenced, for example by a pending
signature. Read the error body first: retrying an update will not clear a duplicate, and
retrying a delete will not clear a dependency.
Notification settings¶
GET /api/customers/{id}/notifications/settings
PUT /api/customers/{id}/notifications/settings
POST /api/customers/{id}/notifications/verify
POST /api/verify-contact
Settings belong to the customer and carry a list of recipients. Each recipient is verified on its own, and nothing is delivered to one until its verification completes. Two calls make that happen: one asks Paros to send the verification, the other is the recipient confirming it. Together they form a single flow.
Open the diagram full screen ↗
What you can configure¶
Settings live on the customer, one entry per recipient. For each recipient you control:
- Source. A team member from the directory, or an external address you add yourself.
- Channels. Email and Telegram, each with its own verified flag.
- Routing. Which channels carry critical, warning and notice alerts.
- Overrides. Per alert type: default behaviour, always critical, or none.
- Digest. Whether email and Telegram digests are on, the schedule, and the timezone.
Recipients and routing in the reference ↗
Per-playlist alert settings are separate, and live with the playlist: Playlist alert settings ↗
Owners and members¶
An owner can change the whole list: add a recipient, remove one, edit any entry.
A member can change one slot only: their own directory recipient, matched by the wallet
address on their membership. Touching another member's entry is refused, and so is any
external recipient (an email or Telegram address not tied to a membership). The call fails
with 403.
That restriction meets the full-list rule in an awkward but workable way. Because PUT
always submits the complete list, a member has to send the list back exactly as it was with
only their own entry changed. They cannot send a delta, and they cannot drop recipients they
do not recognise.
Read and replace settings¶
GET /api/customers/{id}/notifications/settings returns the current list. PUT on the same
path replaces it:
{ "recipients": [ { "channel": "email", "address": "ops@northwind.example" } ] }
recipients is required and carries the complete list. This replaces the whole set, so a
recipient you leave out is removed. Read the settings first, change the one entry you care
about, send the full list back.
The PUT replaces every recipient
As you can see, sending only one recipient to a customer that had more overwrote the list. This is by design.
Request verification¶
{
"channel": "email",
"address": "ops@northwind.example",
"recipientId": "<uuid>"
}
All three fields are required. Paros sends a message to that address containing a token. This is the request half of the flow: it creates the verification and dispatches it.
Confirm verification¶
{ "token": "<token from the verification message>" }
POST /api/verify-contact is unauthenticated on purpose. The recipient follows the link and
the token is the credential; asking for an access token here would fail for someone who has
none. A 400 means the token is malformed, expired or already used, so request a fresh
verification rather than resending the same token.