# Markets

> Find the events, markets, and outcome tokens to trade. Lists, full event detail, and how to extract the condition_id and token_id every trading endpoint expects.

Every trading endpoint is keyed by IDs that come from this surface:

* **`token_id`** — the outcome you want to buy or sell. Required by
  order placement, outcome-specific orderbook reads, and the `orderbook`
  and `best_quote` stream channels.
* **`condition_id`** — the on-chain market the outcome belongs to.
  Used by [Portfolio](/docs/portfolio) filters, condition-wide position
  operations, and the `trades` and `market_status` stream channels.
* **`event_id`** — the event's `id`. Use it with `market_status` to follow
  newly active cycles and completed resolutions for that event.

These IDs live on the same public endpoints documented here. **No
authentication required** — the markets API is open.

**Base URL:** `https://app.agara.xyz` — every path
below is under `/api/v1`.

## Endpoint summary [#endpoint-summary]

| What you want to do                 | Method | Path                    |
| ----------------------------------- | ------ | ----------------------- |
| Browse / search events              | `GET`  | `/events`               |
| Read one event + its markets        | `GET`  | `/events/{slug}`        |
| List markets (with outcomes inline) | `GET`  | `/markets?source=agara` |

A single event groups one or more markets (e.g. "2025 Presidential
Election" with one binary market per candidate). Each market currently has
exactly two outcomes, including markets in a neg-risk group.
You trade against an **outcome's `token_id`**.

## List events [#list-events]

`GET /api/v1/events?source=AGARA`

Paginated, keyset cursor. Defaults to live events first, then
upcoming events by start time, with finished and settled events last.
Keep `source=AGARA` on the first request and every paginated request.

### Query parameters [#query-parameters]

| Param               | Type                                                        | Notes                                                                                                                                                                                                                |
| ------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `limit`             | int                                                         | 1–100, default 20                                                                                                                                                                                                    |
| `cursor`            | string                                                      | Opaque cursor from a previous response's `next_cursor`                                                                                                                                                               |
| `category`          | string                                                      | Filter to a category slug (`politics`, `sports`, `tennis`, …)                                                                                                                                                        |
| `event_type_bucket` | `games` \| `props`                                          | Narrow to sports games or prop markets                                                                                                                                                                               |
| `filter`            | string                                                      | Named filter, e.g. `sports_live`                                                                                                                                                                                     |
| `source`            | string                                                      | Filter to AGARA with `source=AGARA`                                                                                                                                                                                  |
| `exclude_ended`     | `true` \| `false`                                           | Drop events that have already finished, and events with nothing left to trade. A repeating market is kept while its next round is still being set up, so check the market's `state` before you try to place an order |
| `resolution`        | `all` \| `active` \| `proposed` \| `disputed` \| `resolved` | Filter by settlement status. `active` = not yet in resolution                                                                                                                                                        |
| `sort`              | `time` \| `volume` \| `markets`                             | Secondary sort within each lifecycle group (live always leads); default is soonest-first                                                                                                                             |
| `include_markets`   | `true` \| `false`                                           | Add `listing_markets` with lean market/outcome data for prop and multi-outcome rows (off by default)                                                                                                                 |

`filter=sports_live` is the exception to the response below: it returns
leaf-category sections without pagination. Other filters return the flat,
paginated `events` shape.

### Response [#response]

```json
{
  "events": [
    {
      "id": "1afe337d-ae98-4d43-9d6a-2ba14b8bbe4c",
      "slug": "city-a-vs-city-b",
      "title": "City A vs City B",
      "event_type": "GAME",
      "is_accepting_orders": true,
      "is_live": true,
      "start_time": "2025-11-23T17:00:00.000Z",
      "end_time": null,
      "volume_micro": "0",
      "market_count": 1,
      "primary_category": { "slug": "sports", "label": "Sports" },
      "resolution_status": "ACTIVE",
      "is_group_root": false,
      "main_market_id": null
    }
  ],
  "pagination": {
    "next_cursor": "eyJiIjowLCJ0IjoxNzYzOTE3MjAwLCJ2IjowLCJpIjoiMWFmZTMzN2QtYWU5OC00…",
    "limit": 20
  }
}
```

By default, `events[]` is a **list view** with display metadata rather than
order-ready IDs. Setting `include_markets=true` adds a lean `listing_markets`
subset with outcome token IDs for non-game events: the main market for a
proposition or the top candidate markets for a multi-outcome event. Fetch event
detail by `slug` when you need the event's complete market set and full fields.

`primary_category` is the event's main category as curated by the
operators, and is `null` when the event has none.

`market_count` is how many markets the event covers. It is left out for a
recurring series, which opens a fresh market every cycle and so has no
meaningful total to report. Treat the field as optional and fall back to the
markets you were sent rather than to zero.

`volume_micro` is the event's traded volume in millionths of a collateral unit,
counting only markets that have stopped trading for good: halted, in
resolution, or settled. A market's volume is added within about a minute of it
stopping, so a recurring series grows by one round at a time, and markets that
are open or paused are not included. `sort=volume` orders by this value.

### Examples [#examples]

```bash
curl "$AGARA_BASE_URL/api/v1/events?source=AGARA&category=politics&limit=10"
```

```python
import os, requests

AGARA_BASE_URL = os.environ["AGARA_BASE_URL"]

resp = requests.get(f"{AGARA_BASE_URL}/api/v1/events", params={
    "source": "AGARA",
    "category": "politics",
    "limit": 10,
    "exclude_ended": "true",
})
resp.raise_for_status()
for ev in resp.json()["events"]:
    print(ev["slug"], "—", ev["title"])
```

### Pagination [#pagination]

Pass the previous response's `pagination.next_cursor` back as
`?cursor=`. When it comes back `null`, you've reached the end.

```python
cursor = None
while True:
    params = {"source": "AGARA", "limit": 50}
    if cursor:
        params["cursor"] = cursor
    page = requests.get(f"{AGARA_BASE_URL}/api/v1/events", params=params).json()
    for ev in page["events"]:
        ...
    cursor = page["pagination"]["next_cursor"]
    if cursor is None:
        break
```

## Event detail [#event-detail]

`GET /api/v1/events/{slug}`

The full event, its markets, and every outcome on each market —
including the `condition_id` per market and the `token_id` per
outcome. Recurring series (such as the 5-minute crypto markets)
return the live window plus the nearest upcoming and most recent
settled cycles, not the full cycle history.

### Response (trimmed to trading-relevant fields) [#response-trimmed-to-trading-relevant-fields]

```json
{
  "id": "1afe337d-…",
  "slug": "city-a-vs-city-b",
  "title": "City A vs City B",
  "is_accepting_orders": true,
  "is_live": true,
  "resolution_status": "ACTIVE",
  "markets": [
    {
      "slug": "will-city-a-win",
      "question": "Will City A win?",
      "display_label": "City A",
      "exchange": "AGARA",
      "condition_id": "0x03957fd52ec3c1bc1985a566fa8fb7d1e5ee717d3ca5f83f0905aa24fbc33d96",
      "is_accepting_orders": true,
      "tick_size_micro": "1000",
      "min_order_size_micro": "5000000",
      "outcomes": [
        {
          "label": "Yes",
          "token_id": "95507039935216817251467799435356219871689771437585117346620907704908494213017",
          "price_micro": "27000",
          "best_bid_micro": "26000",
          "best_ask_micro": "28000"
        },
        {
          "label": "No",
          "token_id": "39084258401822540125900462921743234567890123456789012345678901234567890123456",
          "price_micro": "973000",
          "best_bid_micro": "972000",
          "best_ask_micro": "974000"
        }
      ]
    }
  ]
}
```

Real responses carry more fields (category breadcrumbs, sport
enrichment, market tabs, display bags); the table below is the
subset that matters for trading.

### Fields you'll use [#fields-youll-use]

| Path                                           | Use it for                                                                                                                                 |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `id`                                           | Event-scoped [`market_status`](/docs/streaming#event-scoped-lifecycle-updates) subscriptions; send it as `event_id`                        |
| `markets[].condition_id`                       | [Positions filter](/docs/portfolio#positions), [`market_status`](/docs/streaming#channels) channel                                         |
| `markets[].outcomes[].token_id`                | [Place order](/docs/orders#place-an-order), [Orderbook](/docs/orderbook), [streaming](/docs/streaming) `orderbook` / `best_quote` channels |
| `markets[].is_accepting_orders`                | If `false`, orders on this market's tokens will be rejected with `422 market_closed`                                                       |
| `markets[].tick_size_micro`                    | Smallest price increment, in micro-probability — your `price_micro` must be a multiple of this                                             |
| `markets[].min_order_size_micro`               | Catalogue metadata; use [Order size limits](/docs/orders#order-size-limits) for enforced AGARA bounds                                      |
| `markets[].exchange`                           | Exchange the market settles on; AGARA docs use `AGARA`                                                                                     |
| `outcomes[].best_bid_micro` / `best_ask_micro` | Top-of-book quote — useful for picking a price without a separate orderbook call                                                           |

### Errors [#errors]

| Status | Reason                                    |
| ------ | ----------------------------------------- |
| `404`  | No event with that slug, or it's archived |

### Examples [#examples-1]

```bash
curl "$AGARA_BASE_URL/api/v1/events/$AGARA_EVENT_SLUG"
```

```python
event_slug = os.environ["AGARA_EVENT_SLUG"]
ev = requests.get(
    f"{AGARA_BASE_URL}/api/v1/events/{event_slug}"
).json()

for m in ev["markets"]:
    if not m["is_accepting_orders"]:
        continue
    outcome = m["outcomes"][0]
    print(m["display_label"], outcome["label"], "token →", outcome["token_id"])
```

## List AGARA-only markets [#list-agara-only-markets]

`GET /api/v1/markets?source=agara`

Dedicated discovery surface for **AGARA-native** markets — every
non-archived market where `exchange == "AGARA"`, with its outcomes
(and `token_id`s) inline. One request hands you everything you need
to place an order. Unauthenticated, like the rest of the markets API.

Use this when you want the full AGARA catalogue with outcomes
inline in a single request. The general [`/events`](#list-events)
endpoint is broader and lighter — use it for browsing and
pagination by event.

### Query parameters [#query-parameters-1]

| Param        | Type   | Notes                                                                |
| ------------ | ------ | -------------------------------------------------------------------- |
| `source`     | string | **Required.** Only `agara` (case-insensitive) is accepted today.     |
| `limit`      | int    | 1–256, default 64                                                    |
| `cursor`     | string | Opaque cursor from a previous response's `next_cursor`               |
| `event_slug` | string | Narrow to markets on a single event                                  |
| `state`      | string | Restrict to one lifecycle state, such as `ACTIVE` (case-insensitive) |

### Response [#response-1]

```json
{
  "markets": [
    {
      "id": "9c8a…",
      "exchange": "AGARA",
      "slug": "will-foo-happen",
      "question": "Will Foo happen by year-end?",
      "display_label": "Will Foo happen by year-end?",
      "display_type": "BINARY",
      "is_accepting_orders": true,
      "volume_micro": "12345678",
      "volume_24h_micro": "1234567",
      "tick_size_micro": "1000",
      "min_order_size_micro": "5000000",
      "line": null,
      "condition_id": "0x4a8b…",
      "neg_risk_id": null,
      "resolution_status": null,
      "outcomes": [
        {
          "id": "…",
          "index": 0,
          "label": "Yes",
          "token_id": "21345678901234567890123456789012345678901234567890123456789012345678901234567",
          "price_micro": "500000",
          "best_bid_micro": "490000",
          "best_ask_micro": "510000"
        },
        {
          "id": "…",
          "index": 1,
          "label": "No",
          "token_id": "98765432109876543210987654321098765432109876543210987654321098765432109876543",
          "price_micro": "500000",
          "best_bid_micro": "490000",
          "best_ask_micro": "510000"
        }
      ],
      "event": {
        "id": "1afe…",
        "slug": "agara-foo-event",
        "title": "Foo Event",
        "active": true
      }
    }
  ],
  "pagination": {
    "next_cursor": "eyJ2ZXIiOjEsImEiOjEsInYiOiIxMjM0NTY3OCIsImkiOiI5YzhhLi4uIiwiZiI6ImE3YjNjOWQxZTVmMiJ9",
    "limit": 64
  }
}
```

Rows are sorted by `is_accepting_orders` (tradeable first), then
`volume_micro` (highest first), then `id` for cursor stability.
`condition_id` is populated once the market is deployed on-chain and remains
available after the market stops accepting orders. It is `null` before
deployment. Trade against the `outcomes[].token_id` directly.

### Examples [#examples-2]

```bash
curl "$AGARA_BASE_URL/api/v1/markets?source=agara&limit=20"
```

```python
import os, requests

AGARA_BASE_URL = os.environ["AGARA_BASE_URL"]

resp = requests.get(f"{AGARA_BASE_URL}/api/v1/markets", params={"source": "agara", "limit": 20})
resp.raise_for_status()
for m in resp.json()["markets"]:
    yes = next((o for o in m["outcomes"] if o["label"].lower() == "yes"), m["outcomes"][0])
    print(m["slug"], "→ YES token", yes["token_id"])
```

### Pagination [#pagination-1]

Same shape as `/events` — pass the previous response's
`pagination.next_cursor` back as `?cursor=`. Reaching the end returns
`next_cursor: null`.

A cursor is tied to the filters of the request that produced it. If you reuse
it while changing `source`, `event_slug`, or `state`, the request fails with
`422 validation_failed` — drop the cursor and start again
from the first page.

## Picking the right outcome [#picking-the-right-outcome]

All markets are currently **binary** — two outcomes labelled "Yes" and "No",
or two team names. Complementary prices sum to one collateral unit, but a SELL requires you to
hold that outcome token; choose the side and order direction that match your
inventory and strategy.

A few patterns to know:

* **Yes/No prop**: `outcomes[].label` is literally `"Yes"` / `"No"`.
* **Team moneyline**: `outcomes[].label` is the team name. The
  helper field `short_label` (e.g. `"LAL"`, `"BOS"`) is good for
  compact UIs.
* **Over/Under totals**: `outcomes[].label` is `"Over"` / `"Under"`;
  the threshold lives on the parent market's `line` field.
* **Multi-outcome** (elections, championships): one event holds many
  binary markets, one per candidate. Each market has its own
  `condition_id` and a Yes/No pair of `token_id`s. Pick the market
  for the candidate you want, then the YES outcome to bet on them
  winning.
* **Three-way moneyline**: Home, Draw and Away are three separate binary
  markets, each with its own YES and NO tokens; there is no three-outcome CTF
  condition.

Neg-risk works the same way at the market level: it groups mutually exclusive
binary markets, but every member keeps its own condition, two outcome tokens,
and orderbook.

## End-to-end: from slug to placed order [#end-to-end-from-slug-to-placed-order]

```python
import os, requests

AGARA_BASE_URL = os.environ["AGARA_BASE_URL"]
TOKEN = os.environ["AGARA_TOKEN"]
HEADERS = {"Authorization": f"Bearer {TOKEN}"}

# 1. Find a tradeable AGARA event, then fetch its full detail.
listing = requests.get(
    f"{AGARA_BASE_URL}/api/v1/events",
    params={"source": "AGARA", "exclude_ended": "true", "limit": 20},
).json()
event_slug = next(e["slug"] for e in listing["events"] if e["is_accepting_orders"])
ev = requests.get(f"{AGARA_BASE_URL}/api/v1/events/{event_slug}").json()

# 2. Pick a market and either of its two outcomes.
market = next(m for m in ev["markets"] if m["is_accepting_orders"])
outcome = market["outcomes"][0]
token_id = outcome["token_id"]

# 3. Trade the token. From here every call is the standard
#    /trade/v1 surface — see Orders / Orderbook / Portfolio.
order = requests.post(f"{AGARA_BASE_URL}/trade/v1/orders", headers=HEADERS, 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),
}).json()
print("placed", order["order_id"])
```

## Follow newly active and resolved markets [#follow-newly-active-and-resolved-markets]

Use REST to discover events, then keep following an event through the public
[`market_status` channel](/docs/streaming#market_status) at
`/trade/v1/market-stream`:

1. List events with `GET /api/v1/events?source=AGARA` and save each event's
   `id` and `slug`. Refresh the listing when you need to discover new events;
   the stream has no subscription covering all events or all new markets.
2. Subscribe to `market_status` with the event's `id` as `event_id`. Begin
   buffering updates, then fetch `GET /api/v1/events/{slug}` for the current
   state. The subscription sends no initial snapshot or replay.
3. Handle `current_market_changed` when a recurring cycle becomes active.
   Its `market` includes the new `condition_id`, outcome `token_id` values,
   opening price when known, and cycle times.
4. Handle `market_resolution_completed` for the resolved market's IDs,
   `winning_token_id`, and resolution time. A split resolution has an empty
   `winning_token_id` and a `payouts` list with what one share of each
   outcome pays. Check `replacement_status`:
   a replacement can be active, upcoming, expected but not created yet, or
   absent. This message does not confirm onchain payout completion.
5. Subscribe separately with the active market's `condition_id` for halts,
   resumptions, and resolution, and its `token_id` values for books and quotes.
   Complete [quote-readiness checks](/docs/api-traders/recovery#quote-readiness-state-machine)
   before trading it.

Through REST, a resolved market's `resolution_outcome` is the label of the
winning outcome, and each outcome's `resolution_price_micro` is what one share
pays: `"1000000"` for the winner and `"0"` for the other outcome. On a
[split](/docs/automatic-redemption#split-resolutions), `resolution_outcome` is
`null` and each `resolution_price_micro` carries that outcome's price, such as
`"370000"` and `"630000"`. While `resolution_status` is `PROPOSED`, these
fields show the proposal, which can still change; treat them as final only
once it is `RESOLVED`.

On reconnect or reset, buffer updates and refresh the event detail again. An
older market's resolution must not replace a newer active cycle you already
know about. See [event-scoped lifecycle updates](/docs/streaming#event-scoped-lifecycle-updates)
for subscription examples, payloads, replacement states, and recovery rules.

## What's next [#whats-next]

* [Quickstart](/docs/quickstart) — discover, place, and watch an order
  end-to-end in one script.
* [Orders](/docs/orders) — every order operation, with the `token_id`
  you just discovered.
* [Orderbook](/docs/orderbook) — live depth for a `token_id`.
* [Streaming](/docs/streaming) — subscribe to `orderbook` /
  `best_quote` by `token_id`, and `market_status` by `condition_id` or `event_id`.
* [Market data API reference](/docs/api-reference/market-data) — every discovery
  endpoint, parameter, and status code.
