Match a contract
curl --request GET \
--url https://surfaceapi.com/api/v1/match/{contractID} \
--header 'X-API-Key: <api-key>'import requests
url = "https://surfaceapi.com/api/v1/match/{contractID}"
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/match/{contractID}', 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/match/{contractID}",
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/match/{contractID}"
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/match/{contractID}")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://surfaceapi.com/api/v1/match/{contractID}")
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{
"contracts": {
"kalshi": {
"KXNBAGAME-26MAR07LACMEM-LAC": {
"parent_market_id": "KXNBAGAME-26MAR07LACMEM",
"question": "Los Angeles C at Memphis Winner?",
"yes_name": "Los Angeles C",
"no_name": "Memphis",
"is_closed": true
},
"KXNBAGAME-26MAR07LACMEM-MEM": {
"parent_market_id": "KXNBAGAME-26MAR07LACMEM",
"question": "Los Angeles C at Memphis Winner?",
"yes_name": "Memphis",
"no_name": "Los Angeles C",
"is_closed": true
}
},
"polymarket": {
"nba-lac-mem-2026-03-07": {
"parent_market_id": "nba-lac-mem-2026-03-07",
"question": "Clippers vs. Grizzlies",
"yes_name": "Clippers",
"no_name": "Grizzlies",
"is_closed": true
}
}
},
"matches": {
"kalshi": {
"KXNBAGAME-26MAR07LACMEM-LAC": {
"polymarket": {
"nba-lac-mem-2026-03-07": {
"is_inverse": false
}
}
},
"KXNBAGAME-26MAR07LACMEM-MEM": {
"polymarket": {
"nba-lac-mem-2026-03-07": {
"is_inverse": true
}
}
}
}
}
}Endpoints
Match a contract
Bidirectional cross-exchange contract lookup. Given any contract ID — a Kalshi ticker or a Polymarket contract slug — returns its full cluster. Contracts and match links are nested in maps keyed by exchange and contract ID. Resolution rules are omitted by default; set include_rules=true to include them. A match lookup is only counted against your daily quota if it is a new cluster; repeat lookups within that cluster are free.
GET
/
match
/
{contractID}
Match a contract
curl --request GET \
--url https://surfaceapi.com/api/v1/match/{contractID} \
--header 'X-API-Key: <api-key>'import requests
url = "https://surfaceapi.com/api/v1/match/{contractID}"
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/match/{contractID}', 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/match/{contractID}",
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/match/{contractID}"
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/match/{contractID}")
.header("X-API-Key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://surfaceapi.com/api/v1/match/{contractID}")
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{
"contracts": {
"kalshi": {
"KXNBAGAME-26MAR07LACMEM-LAC": {
"parent_market_id": "KXNBAGAME-26MAR07LACMEM",
"question": "Los Angeles C at Memphis Winner?",
"yes_name": "Los Angeles C",
"no_name": "Memphis",
"is_closed": true
},
"KXNBAGAME-26MAR07LACMEM-MEM": {
"parent_market_id": "KXNBAGAME-26MAR07LACMEM",
"question": "Los Angeles C at Memphis Winner?",
"yes_name": "Memphis",
"no_name": "Los Angeles C",
"is_closed": true
}
},
"polymarket": {
"nba-lac-mem-2026-03-07": {
"parent_market_id": "nba-lac-mem-2026-03-07",
"question": "Clippers vs. Grizzlies",
"yes_name": "Clippers",
"no_name": "Grizzlies",
"is_closed": true
}
}
},
"matches": {
"kalshi": {
"KXNBAGAME-26MAR07LACMEM-LAC": {
"polymarket": {
"nba-lac-mem-2026-03-07": {
"is_inverse": false
}
}
},
"KXNBAGAME-26MAR07LACMEM-MEM": {
"polymarket": {
"nba-lac-mem-2026-03-07": {
"is_inverse": true
}
}
}
}
}
}string
required
Any Kalshi contract ticker or Polymarket contract slug. See Understanding contract ID for the difference between tickers and slugs.
boolean
default:"false"
Include the exchange resolution rules in each contract. Rules are omitted by default because they can be large.
Authorizations
Path Parameters
Any Kalshi contract ticker or Polymarket contract ID.
Query Parameters
Include the exchange resolution rules in each contract. Omitted by default because rules can be large.