# Where to query Polymarket data with GraphQL

> Polymarket's three REST APIs have no GraphQL endpoint between them. GraphQL access exists, but it runs against open-source subgraphs hosted elsewhere — and they index on-chain events, not the market text and live prices most people are looking for.

- Source: https://papaproxy.net/blog/polymarket-graphql-api.php
- Published: 2026-08-09
- Author: Alex Young
- Category: API & data · PapaProxy.net Blog

---

## Key takeaways

- Polymarket's documented first-party surface — Gamma, CLOB and the Data API — is HTTP plus CLOB WebSocket channels; no GraphQL endpoint is documented for any of the three hosts as of August 2026.
- GraphQL access comes from Polymarket's open-source subgraph, which the docs describe as hostable by anyone, with `schema.graphql` in the public repo as the field-level reference.
- The work is split across subgraphs — positions, orderbook, activity, open interest and P&L — reachable at Goldsky public endpoints whose URLs pin a specific version, or through The Graph's gateway with an API key; the documented 100,000-queries-a-month Free Plan is The Graph's, not Goldsky's.
- Before any historical backfill, check which exchange contracts the deployment indexes: the public manifest still lists the original CTF Exchange while trading has moved to a newer one, so an old deployment may be incomplete.
- The listed subgraphs don't replace Gamma for canonical market text or the CLOB for the live book, which is why subgraph-based products join back to Gamma on condition ID.
- A 200 status doesn't mean the query succeeded — check the `errors` field too — and page by a cursor (`id_gt`) rather than deep `skip`, with `first` capped at 1,000.
- Third-party GraphQL indexers such as Bitquery are separate vendors with their own schemas, retention and pricing — not GraphQL versions of Polymarket's own APIs.

## The REST APIs have no GraphQL endpoint

As of August 2026, [Polymarket's three REST APIs](/blog/polymarket-three-apis.php) — Gamma, the CLOB and the Data API — are documented as HTTP APIs in REST style: paths, query parameters, JSON responses, an OpenAPI schema per endpoint. No GraphQL endpoint is documented for any of the three hosts, and there is no `/graphql` route to discover on `gamma-api.polymarket.com` or `clob.polymarket.com`. Note the precise claim — the CLOB isn't request-response only either, since it publishes [WebSocket market and user channels](/blog/polymarket-websocket-api.php); what's absent everywhere is a documented GraphQL surface.

That's worth stating plainly because the search demand suggests otherwise. People look for a GraphQL endpoint for markets on Gamma, or a GraphQL schema for markets they can introspect, and come away assuming the documentation is incomplete. It isn't: the HTTP and WebSocket surfaces are the whole first-party offering. What exists instead is a separate, genuinely GraphQL-shaped layer built on blockchain indexing, described in Polymarket's own developer documentation under a section of its own.

The distinction matters more than it looks, because the two layers hold different data. The first-party APIs serve what Polymarket's own systems know: market questions, slugs, tags, images, order books, current prices, wallet positions with computed P&L. The subgraph layer serves what the blockchain recorded: conditions, token transfers, trades, splits, merges, redemptions, positions derived from on-chain events. Some things live in both. The things people most often want from GraphQL — a market's title and its live price — are not what the listed subgraphs are built to hold.

## What the subgraphs index

The official position is short: Polymarket has written and open-sourced a subgraph that exposes aggregate calculations and event indexing over volume, user positions, market and liquidity data through a GraphQL interface, updating in real time, and — the part that shapes everything downstream — **it can be hosted by anyone**. The schema lives in `schema.graphql` in the public repository, which is the authoritative reference for what fields exist.

In practice the work is split across several subgraphs rather than one. Polymarket's own agent documentation lists the Goldsky-hosted set:

| Subgraph | What it indexes |
| --- | --- |
| `positions-subgraph` | User token balances |
| `orderbook-subgraph` | Order book and trade events |
| `activity-subgraph` | Splits, merges, redemptions |
| `oi-subgraph` | Market and global open interest |
| `pnl-subgraph` | User position profit and loss |

Endpoints follow a fixed pattern — `https://api.goldsky.com/api/public/project_.../subgraphs/<name>/<version>/gn` — with the version pinned per subgraph, so a URL copied from a year-old tutorial may point at a version that no longer matches the current schema. Check the version before trusting a query you found online.

The alternative host is the decentralized network: a subgraph on The Graph is queried through the gateway at `https://gateway.thegraph.com/api/{api-key}/subgraphs/id/{id}`, which requires an API key from Graph Explorer. Note which provider the free allowance belongs to — Goldsky exposes public Polymarket subgraph endpoints that need no key, while The Graph's Free Plan is the one documented at 100,000 queries per month, with a paid usage-based plan beyond it. That's the practical reason to prefer the gateway for anything you intend to keep running: a public endpoint you don't control can change or disappear, while a keyed gateway query has a quota you can reason about and a paid tier to grow into.

Because the subgraph is open source and hostable by anyone, a third option exists: run it yourself. Exceeding a free tier isn't the reason to — that's what the paid plan is for. Self-hosting earns its cost when you need a modified schema, full control over indexing, guaranteed data properties, or no third-party dependency in your data path.

**One caveat before any historical backfill.** Polymarket's public subgraph manifest still lists the original CTF Exchange among the contracts it indexes, while the current trading stack runs on a newer exchange deployment. A subgraph only sees the contracts its manifest names, so an older deployment — including a pinned Goldsky version copied from a tutorial — may not carry the complete recent trade history. V2-aware deployments exist. Before trusting any backfill, check which exchange contracts the specific deployment indexes, and sanity-check its recent data against a REST source you already trust.

**Third-party GraphQL services are a separate category.** Providers such as Bitquery also index Polymarket activity and expose it over GraphQL, sometimes with market metadata and aggregations the official subgraphs don't carry. They're independent products with their own schemas, retention, authentication and pricing — not GraphQL versions of Gamma, the CLOB or the Data API, and not maintained by Polymarket. Useful, but evaluate them as vendors rather than as an official surface.

## Writing a query against markets and prices

Here's a working GraphQL query for markets — or rather, the honest version of that request. Against a public Goldsky endpoint, no key needed:

BashCopy code

```bash
curl -X POST \
  https://api.goldsky.com/api/public/project_cl6mb8i9h0003e201j6li0diw/subgraphs/orderbook-subgraph/0.0.1/gn \
  -H "Content-Type: application/json" \
  -d '{"query": "query { orderbooks(first: 5) { id tradesQuantity scaledCollateralVolume } }"}'
```

Run it and the shape of the answer explains the section title. You get an `id` — a token or condition identifier — with trade counts and volume. You do not get "Will X happen by December?", because that string was never on-chain. The same applies to prices: the subgraph knows trades that executed, not the current best bid and ask sitting in the CLOB's book.

So a realistic pipeline uses both layers, joined on the condition ID:

PythonCopy code

```python
import requests

GOLDSKY = ("https://api.goldsky.com/api/public/project_cl6mb8i9h0003e201j6li0diw"
           "/subgraphs/positions-subgraph/0.0.7/gn")
GAMMA = "https://gamma-api.polymarket.com"

# Fields below follow the UserPosition entity in the public schema.graphql.
# Deployments differ — run the introspection query at the bottom before trusting this.
QUERY = """
query Positions($first: Int!, $lastId: String!) {
  userPositions(first: $first, where: { id_gt: $lastId }, orderBy: id) {
    id
    user
    tokenId
    amount
    avgPrice
    realizedPnl
    totalBought
  }
}
"""

def gql(url, query, variables=None):
    r = requests.post(url, json={"query": query, "variables": variables or {}}, timeout=30)
    r.raise_for_status()                          # HTTP layer
    payload = r.json()
    if payload.get("errors"):                     # GraphQL layer — a 200 can still be a failure
        raise RuntimeError(payload["errors"])
    return payload["data"]

def all_positions(page=500):                      # cursor paging: no deep skip
    last_id, out = "", []
    while True:
        rows = gql(GOLDSKY, QUERY, {"first": page, "lastId": last_id})["userPositions"]
        if not rows:
            return out
        out.extend(rows)
        last_id = rows[-1]["id"]

def market_title(condition_id):                   # canonical text: Gamma, not the subgraph
    r = requests.get(f"{GAMMA}/markets", params={"condition_ids": condition_id}, timeout=15)
    r.raise_for_status()
    rows = r.json()
    return rows[0]["question"] if rows else None

# Check the schema of the deployment you're actually calling:
INTROSPECT = '{ __type(name: "UserPosition") { fields { name type { name kind } } } }'
print(gql(GOLDSKY, INTROSPECT))
```

What to look at. A successful HTTP status does not guarantee a successful GraphQL operation: the response can arrive as `200` with an `errors` array in the body, so check both layers — `raise_for_status()` for one and the `errors` field for the other. Paging uses a cursor here rather than `skip`: `first` is capped at 1,000 per query by the indexer, and deep `skip` values get slow, so walking `id_gt` from the last row you saw stays fast at any depth. Most importantly, run that introspection query first. Field names differ between subgraph deployments and versions — published guides show different shapes for the same endpoint — so the schema of the deployment you're calling is the only authority.

What this doesn't do: no retry policy, no caching of the Gamma lookups (titles never change, so they belong in a local store), and no rate-limit handling for either layer.

## Subgraph or REST: choosing between them

The choice isn't really GraphQL or REST API as competing styles — it's a question of which layer holds your answer.

| You want | Layer |
| --- | --- |
| Market question, slug, tags, images, dates | REST (Gamma) |
| Live order book, current price, midpoint | REST (CLOB) |
| A wallet's current positions with computed P&L | REST (Data API) — simplest path |
| Historical trades and volume aggregates | Subgraph |
| Splits, merges, redemptions as events | Subgraph |
| Open interest over time | Subgraph |
| Arbitrary aggregation over on-chain history | Subgraph |

For current wallet positions, the [Data API `/positions` endpoint](/blog/polymarket-data-api-positions.php) remains the simplest REST path. Two decision rules cover most cases. If the data is a current value that Polymarket computes, REST is both simpler and fresher. If the data is historical, event-shaped, or needs aggregating in ways no REST endpoint offers, the subgraph is the tool — that's precisely what an indexer is for.

On GraphQL or SDK: the two aren't alternatives either. Polymarket's clients wrap the REST surfaces, and community packages add subgraph clients on top of the same endpoints, which saves you writing HTTP plumbing but doesn't change what each layer contains. An SDK that offers "GraphQL client" alongside CLOB, Gamma and Data clients is wrapping exactly the split described here.

One practical asymmetry decides many architectures: the listed Polymarket subgraphs don't replace Gamma as the canonical source of human-readable metadata, or the CLOB as the live order book. So almost any user-facing product built on subgraph data ends up calling Gamma anyway to render titles. Plan for that join from the start — resolve condition IDs to titles once, cache them, and let the GraphQL layer do what it's good at.

That REST half is where the per-IP limits from [Polymarket's rate-limit guide](/blog/polymarket-api-rate-limits.php) apply — `/markets` at 300 requests per 10 seconds, `/positions` at 150 — and where a wide backfill or a busy dashboard runs into them. Subgraph quotas are separate and attach to your API key rather than your address. If the REST side of your pipeline is the constraint, our [proxies for crypto projects](/crypto-proxy.php) are dedicated IPv4 addresses, static for the full plan term, each subject to its own documented allowance; the GraphQL side scales by query quota instead, so the two halves are sized independently.
