curl --request POST \
--url https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"games": [
{
"startsAt": "2026-08-15T18:00:00Z",
"sport": "Baseball",
"location": "Field 2, Springfield Park",
"payDollars": 65,
"label": "Umpire",
"externalId": "game_8801"
}
]
}
'import requests
url = "https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games"
payload = { "games": [
{
"startsAt": "2026-08-15T18:00:00Z",
"sport": "Baseball",
"location": "Field 2, Springfield Park",
"payDollars": 65,
"label": "Umpire",
"externalId": "game_8801"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
games: [
{
startsAt: '2026-08-15T18:00:00Z',
sport: 'Baseball',
location: 'Field 2, Springfield Park',
payDollars: 65,
label: 'Umpire',
externalId: 'game_8801'
}
]
})
};
fetch('https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games', 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://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'games' => [
[
'startsAt' => '2026-08-15T18:00:00Z',
'sport' => 'Baseball',
'location' => 'Field 2, Springfield Park',
'payDollars' => 65,
'label' => 'Umpire',
'externalId' => 'game_8801'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games"
payload := strings.NewReader("{\n \"games\": [\n {\n \"startsAt\": \"2026-08-15T18:00:00Z\",\n \"sport\": \"Baseball\",\n \"location\": \"Field 2, Springfield Park\",\n \"payDollars\": 65,\n \"label\": \"Umpire\",\n \"externalId\": \"game_8801\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"games\": [\n {\n \"startsAt\": \"2026-08-15T18:00:00Z\",\n \"sport\": \"Baseball\",\n \"location\": \"Field 2, Springfield Park\",\n \"payDollars\": 65,\n \"label\": \"Umpire\",\n \"externalId\": \"game_8801\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"games\": [\n {\n \"startsAt\": \"2026-08-15T18:00:00Z\",\n \"sport\": \"Baseball\",\n \"location\": \"Field 2, Springfield Park\",\n \"payDollars\": 65,\n \"label\": \"Umpire\",\n \"externalId\": \"game_8801\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"created": [
{
"id": "<string>",
"leagueId": "<string>",
"sport": "<string>",
"startsAt": "2023-11-07T05:31:56Z",
"location": "<string>",
"latitude": 123,
"longitude": 123,
"source": "<string>",
"externalId": "<string>",
"positions": [
{
"id": "<string>",
"label": "<string>",
"payCents": 123,
"payDollars": 123
}
]
}
]
}
}{
"error": "invalid_json"
}{
"error": "unauthorized"
}{
"error": "league_not_found"
}{
"error": "position_taken"
}Batch create/upsert games (1–500)
Each game with an externalId is an idempotent upsert on (source, externalId). Create makes the game plus exactly one position (label defaults to "Official"). Upsert-update rewrites sport, startsAt, location, latitude, longitude only — positions, pay, and existing claims are never touched, so re-posting a schedule cannot disturb an official who already claimed a slot. Re-using an externalId that belongs to a game in a different league returns 409 external_id_conflict and leaves the existing game untouched (a game cannot switch leagues).
⚠️ Bulk mutation — confirm before sending. One call can rewrite mutable fields on up to 500 existing games. Agents should summarize the target league and game count and get confirmation first.
⚠️ The batch is not transactional. Games are inserted sequentially; if a mid-batch game fails (e.g. invalid_startsAt:<value>), the request returns 400 but earlier games are already persisted. Recovery: fix the input and re-post the whole batch — upserts make this safe when every game has an externalId.
Emits one game.created webhook per persisted game (including upsert-updates).
curl --request POST \
--url https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"games": [
{
"startsAt": "2026-08-15T18:00:00Z",
"sport": "Baseball",
"location": "Field 2, Springfield Park",
"payDollars": 65,
"label": "Umpire",
"externalId": "game_8801"
}
]
}
'import requests
url = "https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games"
payload = { "games": [
{
"startsAt": "2026-08-15T18:00:00Z",
"sport": "Baseball",
"location": "Field 2, Springfield Park",
"payDollars": 65,
"label": "Umpire",
"externalId": "game_8801"
}
] }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
games: [
{
startsAt: '2026-08-15T18:00:00Z',
sport: 'Baseball',
location: 'Field 2, Springfield Park',
payDollars: 65,
label: 'Umpire',
externalId: 'game_8801'
}
]
})
};
fetch('https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games', 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://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'games' => [
[
'startsAt' => '2026-08-15T18:00:00Z',
'sport' => 'Baseball',
'location' => 'Field 2, Springfield Park',
'payDollars' => 65,
'label' => 'Umpire',
'externalId' => 'game_8801'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games"
payload := strings.NewReader("{\n \"games\": [\n {\n \"startsAt\": \"2026-08-15T18:00:00Z\",\n \"sport\": \"Baseball\",\n \"location\": \"Field 2, Springfield Park\",\n \"payDollars\": 65,\n \"label\": \"Umpire\",\n \"externalId\": \"game_8801\"\n }\n ]\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"games\": [\n {\n \"startsAt\": \"2026-08-15T18:00:00Z\",\n \"sport\": \"Baseball\",\n \"location\": \"Field 2, Springfield Park\",\n \"payDollars\": 65,\n \"label\": \"Umpire\",\n \"externalId\": \"game_8801\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://whistle.huddleup-sports.com/api/v1/leagues/{leagueId}/games")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"games\": [\n {\n \"startsAt\": \"2026-08-15T18:00:00Z\",\n \"sport\": \"Baseball\",\n \"location\": \"Field 2, Springfield Park\",\n \"payDollars\": 65,\n \"label\": \"Umpire\",\n \"externalId\": \"game_8801\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"created": [
{
"id": "<string>",
"leagueId": "<string>",
"sport": "<string>",
"startsAt": "2023-11-07T05:31:56Z",
"location": "<string>",
"latitude": 123,
"longitude": 123,
"source": "<string>",
"externalId": "<string>",
"positions": [
{
"id": "<string>",
"label": "<string>",
"payCents": 123,
"payDollars": 123
}
]
}
]
}
}{
"error": "invalid_json"
}{
"error": "unauthorized"
}{
"error": "league_not_found"
}{
"error": "position_taken"
}Authorizations
Partner API key: Authorization: Bearer whk_…. Keys are issued by the Whistle team (email tommy@huddleup-sports.com), shown once at issuance, and bound to your source namespace — they access only your leagues (plus explicit grants) and your webhook endpoints. Any auth failure returns 401 {"error":"unauthorized"}; the API never returns 403 — out-of-scope resources answer 404 exactly like nonexistent ones.
Path Parameters
Whistle league id (id from league responses).
Body
Response
Persisted games (note the extra created wrapper)
Show child attributes
Show child attributes

