Overview
If your product trades on behalf of end users, the tempting shortcut is to run everyone through one account and track ownership in your own database. It works until the first time you need to answer a real question: what is this user’s PnL, what happens if they want to withdraw, and how do you stop one user’s losses touching another’s collateral. Polymarket has no concept of sub-accounts. Bravado provides one through master-key provisioning: your partner key can create end users, each with their own wallet and their own scoped API key. This guide covers provisioning them safely, including the one place where a deterministic idempotency key is the right call.Read time: about 13 minutes. Backend engineering context assumed. This is server-side work throughout.
TL;DR
- A master key carries
trade.usersand can provision. A user key is bound to one wallet. POST /v2/trade/userscreates a wallet and returns a key bound to it. The key is shown once.- Use a deterministic idempotency key here, derived from your user id, so a retry cannot mint a second wallet.
- Keys live server-side. Never ship one to a browser or mobile app.
- Scope user keys to what they need.
trade.withdrawis not included by default and should usually stay off. - Rate limits are per key, so each user gets their own budget rather than competing for one.
What you will do
- Confirm your master key can provision
- Create a user with an identifier from your own system
- Store the returned key safely and map it to your user
- Understand why this call in particular needs a deterministic idempotency key
- Scope user keys to limit blast radius
- Show a user their own balances, positions, and history
What you will need
Knowledge- Backend engineering, and a secrets store you trust
- A Bravado master key with the
trade.usersscope - Somewhere encrypted to keep per-user keys
Confirm the master key
api_key.scopescontainstrade.usersbindingdescribes the partner rather than a single user
trade.users cannot provision, and the failure is a 403 that looks like an auth problem rather than a scope problem.
Key types
Provision a user
external_id so you can map the result back to your user record without maintaining a separate lookup.
Why this call needs a deterministic key
Everywhere else in these guides, the advice is a fresh UUID per intended action. Provisioning is the exception. A duplicate order is bad. A duplicate wallet is worse: you now have one user with two wallets, funds split across them, positions in both, and no clean way to merge. Nothing in the API will stop you, because two provisioning calls with different idempotency keys are two legitimate requests. Keying on your own user id makes that impossible:This is the opposite of the guidance in Safe retries, and deliberately so. There, a random key per intended order is right because two identical orders are usually two real intentions. Here, two provisioning calls for the same user are never two intentions.
Architecture
1
User signs up in your product
They never see Bravado. Your app is the entire interface.
2
Your backend provisions
Call
POST /v2/trade/users with the master key and your user id.3
Store the key server-side
Encrypted, mapped to your user. It never leaves your infrastructure.
4
Trade on their behalf
Use that user’s key so positions, balances, and PnL are attributed to them.
Scope user keys deliberately
Scopes limit what a compromised or misused key can do:trade.withdraw is not included in a standard partner key and must be requested explicitly. If withdrawals run through your own flow rather than per-user, keep it off user keys entirely. It is the one scope where a mistake is irreversible.Per-user rate limits
Limits are per key, so a hundred provisioned users have a hundred budgets rather than sharing one:Show a user their account
Everything a normal integration does works with that user’s key:Wrapping up
One master key provisions many users, each with a real wallet and a scoped key. That gives you per-user attribution, per-user rate limits, and per-user risk isolation without building any of it. Two rules carry the weight: keys stay on your backend, and provisioning uses a deterministic idempotency key. The first prevents a class of compromise; the second prevents a mess that is genuinely painful to unwind.Frequently asked questions
Can I put a user key in my mobile app?
Can I put a user key in my mobile app?
No. It can be extracted, and it can trade. Route every request through your backend.
Why deterministic idempotency here but random elsewhere?
Why deterministic idempotency here but random elsewhere?
Two orders with the same parameters are usually two real intentions, so a random key per order is correct. Two provisioning calls for the same user are never two intentions, so keying on the user id makes duplicates impossible.
What if I lose a user's key?
What if I lose a user's key?
It is shown once at creation. Contact support to rotate it rather than provisioning again, since a second provisioning call with a different identifier creates a second wallet.
Should user keys have trade.withdraw?
Should user keys have trade.withdraw?
Usually not. It is excluded by default and must be requested. If your product handles withdrawals through its own flow, keeping it off user keys removes the possibility of funds leaving by an unintended path.
Can I show a user their tax statement?
Can I show a user their tax statement?
Yes. Their wallet is a real on-chain address, so every Data API endpoint works on it, including R1 dispositions and per-year tax reports. See Generate a tax statement.
Resources
- Account and provisioning reference,
POST /v2/trade/usersand scopes - Authentication, keys, scopes, and error handling
- Safe retries, the general idempotency rule this one departs from
- Build a trading terminal, the UI on top of this
- Data API, per-user history and reporting
- Rate limits, per-key budgets