Start here

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's cast (to create a test wallet and sign), or Node 18+ with @agg-build/sdk and viem for 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

SDK
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.

SDK
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 you

Full 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:

ResponseFix
401 with code: "unregistered_domain"DOMAIN is not one of the app's allowed origins.
401 Invalid SIWE signatureThe 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 limitThe 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

SDK
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:

SDK
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.

SDK
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.

SDK
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:

statusFix
insufficient_balanceFund the account (step 2) or lower maxSpend.
min_order_size_violated, insufficient_input_amountRaise maxSpend.
insufficient_depth, no_orderbooksPick 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

SDK
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.

SDK
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:

overallStateMeaning
filledEvery order filled. Done.
partially_filledSome of the trade filled. Each order's status and partialFillReason say how much and why.
failedNothing filled. Read errorReason and each order's errorReason.
cancelled, expiredNothing 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.

On this page