> ## Documentation Index
> Fetch the complete documentation index at: https://docs.surfaceapi.com/llms.txt
> Use this file to discover all available pages before exploring further.

# List match clusters

> Return a paginated list of clusters for exactly one configured series/tag association. Each cluster uses exchange-keyed contract and match maps. A page is charged atomically against the daily unique-cluster quota.

Return a paginated list of production match clusters for one configured association. Each list entry uses the same exchange-keyed cluster shape as `GET /match/{contractID}`.

<ParamField query="scope_id" type="string" required>
  One Kalshi series ID or Polymarket tag ID. It must resolve to exactly one configured association; a tag shared by multiple series is ambiguous.
</ParamField>

<ParamField query="status" type="string">
  Filter clusters by `open`, `closed`, or `settling`. Omit it to include all states. A cluster is settling when its linked contracts include both open and closed outcomes.
</ParamField>

<ParamField query="sort_by" type="string" default="close_time">
  Sort by `close_time` or `open_time`. The cluster uses the latest timestamp among its linked contracts. Null timestamps sort last.
</ParamField>

<ParamField query="order" type="string" default="desc">
  `desc` puts later timestamps first; `asc` puts earlier timestamps first. Equal timestamps use a stable contract-ID tie-break.
</ParamField>

<ParamField query="limit" type="integer" default="25">
  Results per page, from 1 to 100.
</ParamField>

<ParamField query="cursor" type="string">
  Cursor returned in the previous page's `pagination.next_cursor`. Keep the other query parameters unchanged when continuing.
</ParamField>

<ParamField query="include_rules" type="boolean" default="false">
  Include exchange resolution rules in contract metadata. Omit or set false for a smaller response.
</ParamField>

```bash theme={null}
curl "https://surfaceapi.com/api/v1/matches?scope_id=KXMLBGAME&status=open&sort_by=close_time&order=asc&limit=25" \
  -H "X-API-Key: YOUR_API_KEY"
```

```json theme={null}
{
  "scope_id": "KXMLBGAME",
  "status": "open",
  "sort_by": "close_time",
  "order": "asc",
  "clusters": [
    {
      "contracts": {
        "kalshi": {
          "KXMLBGAME-...-SF": { "parent_market_id": "...", "question": "...", "is_closed": false },
          "KXMLBGAME-...-NYY": { "parent_market_id": "...", "question": "...", "is_closed": false }
        },
        "polymarket": {
          "mlb-nyy-sf-2026-03-25": { "parent_market_id": "...", "question": "...", "is_closed": false }
        }
      },
      "matches": {
        "kalshi": {
          "KXMLBGAME-...-SF": {
            "polymarket": { "mlb-nyy-sf-2026-03-25": { "is_inverse": false } }
          },
          "KXMLBGAME-...-NYY": {
            "polymarket": { "mlb-nyy-sf-2026-03-25": { "is_inverse": true } }
          }
        }
      }
    }
  ],
  "pagination": { "limit": 25, "next_cursor": "eyJvZmZzZXQiOjI1fQ..." }
}
```

`pagination.next_cursor` is omitted on the final page. The page is charged against the daily unique-cluster quota atomically. If all new clusters on the requested page would exceed the remaining allowance, the request returns `429` and charges none of that page. Repeated clusters remain free.


## OpenAPI

````yaml GET /matches
openapi: 3.1.0
info:
  title: Surface API
  description: >-
    Cross-exchange prediction market data. Exchange-keyed match clusters between
    supported prediction markets.
  version: 1.0.0
servers:
  - url: https://surfaceapi.com/api/v1
    description: Production
security:
  - apiKey: []
paths:
  /matches:
    get:
      tags:
        - Contracts
      summary: List match clusters
      description: >-
        Return a paginated list of clusters for exactly one configured
        series/tag association. Each cluster uses exchange-keyed contract and
        match maps. A page is charged atomically against the daily
        unique-cluster quota.
      operationId: listMatchClusters
      parameters:
        - name: scope_id
          in: query
          required: true
          description: >-
            A series ID or tag ID that resolves to exactly one configured
            association.
          schema:
            type: string
        - name: status
          in: query
          required: false
          description: Cluster lifecycle state. Omit to include all.
          schema:
            type: string
            enum:
              - open
              - closed
              - settling
        - name: sort_by
          in: query
          required: false
          schema:
            type: string
            enum:
              - close_time
              - open_time
            default: close_time
        - name: order
          in: query
          required: false
          description: >-
            Descending returns later timestamps first. Null timestamps sort
            last.
          schema:
            type: string
            enum:
              - asc
              - desc
            default: desc
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
        - name: cursor
          in: query
          required: false
          schema:
            type: string
        - name: include_rules
          in: query
          required: false
          schema:
            type: boolean
            default: false
      responses:
        '200':
          description: Page of match clusters.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MatchPageResponse'
        '400':
          description: Invalid cursor.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '404':
          description: Unknown scope ID.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '409':
          description: Scope ID matches more than one association.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorResponse'
        '422':
          description: Invalid query parameter value.
        '429':
          description: Request-rate or daily unique-cluster quota exhausted.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/QuotaErrorResponse'
components:
  schemas:
    MatchPageResponse:
      type: object
      required:
        - scope_id
        - status
        - sort_by
        - order
        - clusters
        - pagination
      properties:
        scope_id:
          type: string
        status:
          type: string
          enum:
            - all
            - open
            - closed
            - settling
        sort_by:
          type: string
          enum:
            - close_time
            - open_time
        order:
          type: string
          enum:
            - asc
            - desc
        clusters:
          type: array
          items:
            $ref: '#/components/schemas/ClusterResponse'
        pagination:
          type: object
          required:
            - limit
          properties:
            limit:
              type: integer
            next_cursor:
              type: string
    ErrorResponse:
      type: object
      required:
        - error
        - message
      properties:
        error:
          type: string
          description: Machine-readable error code.
        message:
          type: string
          description: Human-readable description of the error.
    QuotaErrorResponse:
      allOf:
        - $ref: '#/components/schemas/ErrorResponse'
        - type: object
          required:
            - limit
            - used
            - resets_at
          properties:
            limit:
              type: integer
              description: Your daily quota for new unique cluster lookups.
            used:
              type: integer
              description: Number of new unique clusters accessed today.
            resets_at:
              type: string
              format: date-time
              description: RFC3339 UTC timestamp of the next quota reset (midnight UTC).
    ClusterResponse:
      type: object
      required:
        - contracts
        - matches
      properties:
        contracts:
          type: object
          additionalProperties:
            type: object
            additionalProperties:
              $ref: '#/components/schemas/Contract'
          description: Contracts indexed by exchange name, then contract ID.
        matches:
          type: object
          additionalProperties:
            type: object
            additionalProperties:
              type: object
              additionalProperties:
                type: object
                additionalProperties:
                  $ref: '#/components/schemas/MatchLink'
          description: >-
            Nested exchange and contract ID maps to linked contracts and
            relationship metadata.
    Contract:
      type: object
      required:
        - is_closed
      properties:
        parent_market_id:
          type: string
          description: >-
            The parent market or event ID. For Kalshi this is the series+date
            portion; for Polymarket it is the event slug.
        question:
          type: string
          description: The market question as displayed on the exchange.
        yes_name:
          type: string
          description: >-
            Label for the YES outcome (e.g. the team name on a Kalshi
            moneyline).
        no_name:
          type: string
          description: Label for the NO outcome.
        opens_at:
          type: integer
          description: Open timestamp in epoch milliseconds.
        closes_at:
          type: integer
          description: Close timestamp in epoch milliseconds.
        is_closed:
          type: boolean
          description: Whether the contract is closed.
        rules:
          type: string
          description: >-
            Resolution rules for the contract. Included only when
            include_rules=true and available.
    MatchLink:
      type: object
      required:
        - is_inverse
      properties:
        is_inverse:
          type: boolean
          description: >-
            When true, the YES side of one linked contract corresponds to the NO
            side of the other.
  securitySchemes:
    apiKey:
      type: apiKey
      in: header
      name: X-API-Key

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.