List match clusters
curl --request GET \
--url https://surfaceapi.com/api/v1/matches \
--header 'X-API-Key: <api-key>'import requests
url = "https://surfaceapi.com/api/v1/matches"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://surfaceapi.com/api/v1/matches', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://surfaceapi.com/api/v1/matches",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://surfaceapi.com/api/v1/matches"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://surfaceapi.com/api/v1/matches")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://surfaceapi.com/api/v1/matches")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"scope_id": "<string>",
"status": "all",
"sort_by": "close_time",
"order": "asc",
"clusters": [
{
"contracts": {},
"matches": {}
}
],
"pagination": {
"limit": 123,
"next_cursor": "<string>"
}
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>",
"limit": 123,
"used": 123,
"resets_at": "2023-11-07T05:31:56Z"
}Endpoints
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.
GET
/
matches
List match clusters
curl --request GET \
--url https://surfaceapi.com/api/v1/matches \
--header 'X-API-Key: <api-key>'import requests
url = "https://surfaceapi.com/api/v1/matches"
headers = {"X-API-Key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'X-API-Key': '<api-key>'}};
fetch('https://surfaceapi.com/api/v1/matches', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://surfaceapi.com/api/v1/matches",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"X-API-Key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://surfaceapi.com/api/v1/matches"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("X-API-Key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://surfaceapi.com/api/v1/matches")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://surfaceapi.com/api/v1/matches")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["X-API-Key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"scope_id": "<string>",
"status": "all",
"sort_by": "close_time",
"order": "asc",
"clusters": [
{
"contracts": {},
"matches": {}
}
],
"pagination": {
"limit": 123,
"next_cursor": "<string>"
}
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>"
}{
"error": "<string>",
"message": "<string>",
"limit": 123,
"used": 123,
"resets_at": "2023-11-07T05:31:56Z"
}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}.
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.
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.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.string
default:"desc"
desc puts later timestamps first; asc puts earlier timestamps first. Equal timestamps use a stable contract-ID tie-break.integer
default:"25"
Results per page, from 1 to 100.
string
Cursor returned in the previous page’s
pagination.next_cursor. Keep the other query parameters unchanged when continuing.boolean
default:"false"
Include exchange resolution rules in contract metadata. Omit or set false for a smaller response.
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"
{
"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.Authorizations
Query Parameters
A series ID or tag ID that resolves to exactly one configured association.
Cluster lifecycle state. Omit to include all.
Available options:
open, closed, settling Available options:
close_time, open_time Descending returns later timestamps first. Null timestamps sort last.
Available options:
asc, desc Required range:
1 <= x <= 100Response
Page of match clusters.
Available options:
all, open, closed, settling Available options:
close_time, open_time Available options:
asc, desc Show child attributes
Show child attributes
Show child attributes
Show child attributes