Option 1: API Authentication
Make a POST request with your API key:
Unknown top-level body keys are rejected with
1001 VALIDATION_FAILED.
clientData Fields
TheclientData object feeds into the risk model that determines how much instant credit a user qualifies for during deposits. totalWager, kycLevel, fiatDeposits and cryptoDeposits are the fields used in collateral calculations. When you call the API, any other keys inside clientData are dropped; a token you sign locally (Option 2) keeps whatever keys you put in it.
All fields are optional. If omitted, they default to
0 or false in the risk calculation, which means the user gets the baseline collateral amount.
Authenticating only signs a token; nothing is stored at that point. The clientData carried in the token is saved to the user’s record when they create a deposit or withdrawal with it. Issue a new token whenever the values change so the next trade uses current data.
Response:
Option 2: Local Token Generation
If you want to skip the network round-trip, you can sign the token locally using thejose library and your API secret.
merchantId as a top-level claim and also carries it in the JWT header userId field so AssetPay can look up your API secret during verification. If you don’t know your merchant ID, contact support or check your dashboard.
Both methods produce identical tokens. The only difference is whether AssetPay’s server or your server does the signing. Local generation saves one network call per user session.
Using the Token
For all/client/* endpoints, pass the token in the Authorization header:
/auth/authenticate-client, /secure/*), use the api-key header instead:
Security & Architecture
Client tokens can be used directly from your frontend. You don’t need to proxy every API call through your backend. This works because AssetPay protects your balance through callbacks, not through token restrictions: Deposits don’t require any balance check from you. The user is sending you items, not taking them. Your backend gets notified via callbacks when the deposit completes, and you credit the user at that point. Withdrawals are protected by theinitiated callback. When a user initiates a withdrawal, AssetPay sends a callback to your backend before purchasing anything. Your backend checks the user’s balance, deducts it, and responds with 200 to approve. If the balance is insufficient, respond with a rejection and the trade is cancelled. Nothing gets purchased until your backend says so.
This means the typical architecture looks like:
1
Frontend calls AssetPay directly
The user’s browser uses the client token to fetch inventory, browse the market, and initiate trades.
2
AssetPay calls your backend
For withdrawals, AssetPay sends an
initiated callback to your backend. Your backend validates the user’s balance, deducts it, and responds with 2xx to approve the purchase. To reject, respond with a 4xx (e.g. 402 Payment Required); 408 and 429 are retried instead. A 2xx whose body has "action": "reject", "status": "rejected", or an INSUFFICIENT_BALANCE error code also rejects.3
Your backend processes results
As the trade progresses, your backend receives callbacks for each status change. Credit balances on
completed, handle refunds on failed or reverted.You can also call AssetPay from your backend if you prefer. The client token works from both frontend and backend. The callback approval mechanism protects you either way.
What about the API key?
Your API key (ap_...) should stay on your backend. It’s used for merchant-level operations like authenticating clients, fetching prices, and reading your wallet balance and ledger. The client token is the one that’s safe to expose to users.
API Key Scopes
Your API key needs theCORE_ACCESS scope to authenticate clients and use the /secure/* endpoints. Available scopes:
A key without the scope a route needs gets
403 with error 1908 INSUFFICIENT_SCOPE. Grant only the scopes your integration uses.
You can manage scopes and rotate keys from your dashboard.