Agara

Quickstart

Five minutes from no token to discovered market to placed order to confirmed fill. Python, copy-paste runnable, browser-free after the one-time token setup.

0. One-time browser setup

If you haven't signed in yet:

  1. Open the web app, sign in.
  2. Wait for your account setup to finish. The app shows when your on-chain account and trading approvals are ready.
  3. Open Settings → API Tokens, click Create token, and pick the recommended scopes:
    portfolio:read, orders:read, orders:place,
    orders:cancel, orders:cancel_all
    (Reading the orderbook and discovering markets don't need a scope — those endpoints are public.)
  4. Copy the token immediately. It's shown exactly once.

After this you never need the browser again for trading.

1. Setup

pip install requests
export AGARA_BASE_URL="https://app.agara.xyz"
export AGARA_WS_URL="wss://app.agara.xyz"
export AGARA_CHAIN_ID="8453"
export AGARA_COLLATERAL_ADDRESS="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
export AGARA_CONDITIONAL_TOKENS_ADDRESS="0xe12D566cE5Dd8d817488E85a81386878649e3A18"
export AGARA_CTF_EXCHANGE_ADDRESS="0xD06f3fA925D35077A11dB42c6614D684f10B2aD6"
export AGARA_TOKEN="agt_..."
import os, time, requests
 
AGARA_BASE_URL = os.environ["AGARA_BASE_URL"]
TOKEN = os.environ["AGARA_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
 
def call(method, path, json=None, auth=True):
    headers = HEADERS if auth else {}
    r = requests.request(method, f"{AGARA_BASE_URL}{path}", headers=headers, json=json)
    r.raise_for_status()
    return r.json() if r.content else None

2. Discover an event

Find something to trade. Browse, filter by category, exclude finished events — the markets API is public, no auth needed. See Markets for the full list of filters.

listing = call("GET", "/api/v1/events?limit=20&exclude_ended=true&source=AGARA", auth=False)
for ev in listing["events"]:
    print(ev["slug"], "—", ev["title"])

Once you've picked an event, fetch the full detail by its slug. The response carries the event's markets and every outcome on each market — including the token_id you'll trade against. Recurring series return the live window plus their most recent cycles, not the full history.

EVENT_SLUG = next(ev["slug"] for ev in listing["events"] if ev["is_accepting_orders"])
 
ev = call("GET", f"/api/v1/events/{EVENT_SLUG}", auth=False)
 
market  = next(m for m in ev["markets"] if m["is_accepting_orders"])
outcome = market["outcomes"][0]
 
TOKEN_ID = outcome["token_id"]
print(f"trading {market['display_label']}{outcome['label']}{TOKEN_ID[:12]}…")

3. Read the orderbook

book = call("GET", f"/trade/v1/orderbook/{TOKEN_ID}", auth=False)
# Either side can be empty on a brand-new market or one that just
# saw a sweep — guard before indexing.
best_bid = book["bids"][0]["price"] if book["bids"] else None
best_ask = book["asks"][0]["price"] if book["asks"] else None
print(f"best bid {best_bid} / ask {best_ask}")

Sample output:

best bid 0.27 / ask 0.29

4. Place a limit order

Buy 1 share at 0.30 collateral units. Amounts go on the wire in micro units encoded as strings — multiply by 1,000,000, stringify. (See Conventions for why.)

resp = call("POST", "/trade/v1/orders", json={
    "token_id": TOKEN_ID,
    "side": "BUY",
    "type": "LIMIT",
    "time_in_force": "GTC",
    "price_micro": str(int(0.30 * 1_000_000)),
    "shares_micro": str(1 * 1_000_000),
})
 
order_id = resp["order_id"]
print(f"placed order {order_id}, status {resp['status']}")

Sample output:

placed order 9c4a3e7d-12b4-4f8e-9a3c-d2c7f0a45e1b, status PENDING

PENDING is the initial acknowledgement — we've accepted the order and are routing it to the exchange, not that it's filled. It may progress through SUBMITTING and OPEN, fill immediately, or be rejected asynchronously.

5. Watch the order

Poll until it reaches a terminal status, or until you give up and cancel. Response amounts come back as strings — wrap in int(...) when you need to do math on them.

TERMINAL = {"MATCHED", "CANCELLED", "EXPIRED", "REJECTED", "FAILED"}
DEADLINE = time.time() + 30
 
while time.time() < DEADLINE:
    order = call("GET", f"/trade/v1/orders/{order_id}")["order"]
    filled = int(order["size_matched_micro"])
    total = int(order["original_size_micro"])
    print(f"status={order['status']} filled={filled}/{total}")
    if order["status"] in TERMINAL:
        break
    time.sleep(1.0)
else:
    print("not filled in 30s — cancelling")
    call("DELETE", f"/trade/v1/orders/{order_id}")

If it fills:

status=PENDING filled=0/1000000
status=OPEN filled=0/1000000
status=PARTIALLY_FILLED filled=500000/1000000
status=MATCHED filled=1000000/1000000

6. See your fills

trades = call("GET", "/trade/v1/portfolio/trades")
recent = sorted(trades["trades"], key=lambda t: t["executed_at"], reverse=True)[:5]
 
for t in recent:
    shares = int(t["shares_micro"]) / 1e6
    price  = int(t["price_micro"]) / 1e6
    fee    = int(t["fee_micro"]) / 1e6
    print(f"{t['executed_at']}  {t['side']}  {shares:.2f}sh "
          f"@ {price:.4f} collateral  fee={fee:.6f} collateral")

7. Cleanup (optional)

If you have leftover open orders from testing:

call("POST", "/trade/v1/orders/cancel-all")

Next

Full script

import os, time, requests
 
AGARA_BASE_URL = os.environ["AGARA_BASE_URL"]
TOKEN = os.environ["AGARA_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
TERMINAL = {"MATCHED", "CANCELLED", "EXPIRED", "REJECTED", "FAILED"}
 
def call(method, path, json=None, auth=True):
    headers = HEADERS if auth else {}
    r = requests.request(method, f"{AGARA_BASE_URL}{path}", headers=headers, json=json)
    r.raise_for_status()
    return r.json() if r.content else None
 
listing = call(
    "GET",
    "/api/v1/events?limit=20&exclude_ended=true&source=AGARA",
    auth=False,
)
event_slug = next(ev["slug"] for ev in listing["events"] if ev["is_accepting_orders"])
ev = call("GET", f"/api/v1/events/{event_slug}", auth=False)
market = next(m for m in ev["markets"] if m["is_accepting_orders"])
outcome = market["outcomes"][0]
TOKEN_ID = outcome["token_id"]
print(f"trading {market['display_label']}{outcome['label']}")
 
book = call("GET", f"/trade/v1/orderbook/{TOKEN_ID}", auth=False)
best_bid = book["bids"][0]["price"] if book["bids"] else None
best_ask = book["asks"][0]["price"] if book["asks"] else None
print(f"best bid {best_bid} / ask {best_ask}")
 
resp = call("POST", "/trade/v1/orders", json={
    "token_id": TOKEN_ID,
    "side": "BUY",
    "type": "LIMIT",
    "time_in_force": "GTC",
    "price_micro": str(int(0.30 * 1_000_000)),
    "shares_micro": str(1 * 1_000_000),
})
order_id = resp["order_id"]
print(f"placed {order_id}")
 
deadline = time.time() + 30
while time.time() < deadline:
    order = call("GET", f"/trade/v1/orders/{order_id}")["order"]
    filled = int(order["size_matched_micro"]) / 1e6
    print(f"  {order['status']} filled={filled}")
    if order["status"] in TERMINAL:
        break
    time.sleep(1.0)
else:
    print("cancelling")
    call("DELETE", f"/trade/v1/orders/{order_id}")

On this page