Quickstart: REST
From zero to a filled order with curl: sign in, fund, find a market, quote, fill, and track
You'll need:
- An app ID. Sign in to the admin dashboard (
https://admin.agg.market); your first sign-in creates an app. Copy its ID from API. See Base URL, app IDs & API keys. - One allowed origin on that app, for example
https://app.example.com(Domains). A new app has none, and wallet sign-in is refused until you add one. The sign-in message must name it. curl,jq, and Foundry'scast(to create a test wallet and sign), or Node 18+ with@agg-build/sdkandviemfor the SDK tabs.- A few dollars of USDC on a supported chain, or use paper mode.
Result: a filled order, polled until terminal is true.
This guide places a real order on a live venue and spends real funds. Use a small amount, or add
mode=paper to trade without funds. Venues are restricted by the caller's country; see
Venue geo availability.
0. Set up
import { createAggClient, MarketStatus } from "@agg-build/sdk";
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
const client = createAggClient({
baseUrl: "https://api.t.sufwankhalid.com",
appId: "your-app-id",
});
const ORIGIN = "https://app.example.com"; // one of your app's allowed origins
const account = privateKeyToAccount(process.env.PK as `0x${string}` ?? generatePrivateKey());1. Sign the user in
Get a nonce, sign an EIP-4361 (SIWE) message with the wallet, and exchange it for tokens.
const start = await client.authStart({ provider: "siwe" });
if (start.type === "challenge_required") {
throw new Error("Complete the bot-protection challenge, then restart sign-in with its token");
}
if (start.type !== "nonce") throw new Error("Expected a wallet sign-in nonce");
const message = client.buildSiweMessage({
address: account.address,
chainId: 1,
nonce: start.nonce,
domain: new URL(ORIGIN).host,
uri: ORIGIN,
});
const signature = await account.signMessage({ message });
await client.verify({ message, signature }); // the client now sends the token for youFull shapes on Start authentication and Verify sign-in.
POST /auth/verify returns { accessToken, refreshToken, user }. Every call below sends
x-app-id and Authorization: Bearer $ACCESS_TOKEN.
The message format must be exact: a blank statement slot means two empty lines between the address
and URI:. If sign-in fails:
| Response | Fix |
|---|---|
401 with code: "unregistered_domain" | DOMAIN is not one of the app's allowed origins. |
401 Invalid SIWE signature | The message text changed between signing and sending, the address is not checksummed, or the nonce was reused. See Authentication & sessions. |
/auth/start returns "type": "challenge_required" | Bot protection is on for the app. See Bot protection. |
403 testing limit | The app reached 20 users in testing mode. |
Access tokens are short-lived. If a later call returns 401, see Authentication & sessions.
2. Get a deposit address and fund it
let deposit = await client.getDepositAddresses();
while (!deposit.ready) {
await new Promise((r) => setTimeout(r, 2000));
deposit = await client.getDepositAddresses();
}
console.log(deposit.evmAddress, deposit.supportedChains);Full shape on Get deposit addresses. The full 202 {ready:false} loop is on Funding.
Send USDC to evmAddress on one of the chains in supportedChains (or to svmAddress on
Solana). Then check the balance until availableRaw is above zero:
const balances = await client.getManagedBalances();
console.log(balances.cash.map((c) => [c.tokenSymbol, c.availableRaw, c.decimals]));availableRaw is an integer string in the token's decimals: "5000000" with 6 decimals is 5 USDC.
Full shape on Get balances.
3. Find a market
List open markets and take an outcome id. You trade an outcome, not a market.
const markets = await client.getVenueMarkets({
status: MarketStatus.open,
search: "bitcoin",
limit: 5,
});
const outcomeId = markets.data[0]?.venueMarketOutcomes?.[0]?.id;
if (!outcomeId) throw new Error("No open outcome found; try another search");Full shape on List venue markets. See Markets, outcomes & matching and Ids for how ids and cross-venue matching work.
4. Get a quote
Send the user's token so the quote is executable, not a preview.
const quote = await client.getSmartRoute({
venueMarketOutcomeId: outcomeId,
tradeSide: "buy",
maxSpend: 5,
});
if (quote.status !== "ok") throw new Error(`${quote.status}: ${quote.error ?? ""}`);Full shape on Get a quote (smart route). message is informational. Read error when status is not "ok".
Fill only when status is "ok". Otherwise:
status | Fix |
|---|---|
insufficient_balance | Fund the account (step 2) or lower maxSpend. |
min_order_size_violated, insufficient_input_amount | Raise maxSpend. |
insufficient_depth, no_orderbooks | Pick another market. |
The quote expires at expiresAt, about 45 seconds after it is created. Fill within that window.
Details: Quotes & smart routing.
5. Execute the quote
const fill = await client.executeManaged({ quoteId: quote.quoteId });Full shape on Execute quote. The response status is pending: accepted, not done. Poll Order lifecycle. If the call times out or returns 404, follow Errors. Do not re-quote until that page says to.
6. Track it to a final state
Poll the execution status until terminal is true, waiting pollAfterMs between calls.
let status = await client.getExecutionStatus({ quoteId: fill.quoteId });
while (!status.terminal) {
await new Promise((r) => setTimeout(r, status.pollAfterMs ?? 1000));
status = await client.getExecutionStatus({ quoteId: fill.quoteId });
}
console.log(status.overallState, status.orders);Full shape on Get execution status.
The final overallState is one of:
overallState | Meaning |
|---|---|
filled | Every order filled. Done. |
partially_filled | Some of the trade filled. Each order's status and partialFillReason say how much and why. |
failed | Nothing filled. Read errorReason and each order's errorReason. |
cancelled, expired | Nothing filled. |
filledAmountRaw is USD spent and actualSharesRaw is shares bought, both as 6-decimal integer
strings. Every status and state is listed in
Order lifecycle & statuses.
The new position shows up in GET /execution/positions.
Test without real funds
Add mode=paper to the quote, "mode": "paper" to the fill body, and mode=paper to the status
call. Skip step 2: a paper account is created for the user on first use, with a simulated
PAPER_USD balance. Paper fills price against live books but never reach a venue. See
Paper trading.
Next
To expand this integration, use the SDK method reference to find the client method for each API operation.