curl --request POST \
--url https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"externalId": "user_42"
}
'import requests
url = "https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign"
payload = { "externalId": "user_42" }
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({externalId: 'user_42'})
};
fetch('https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign', 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/games/{gameId}/assign",
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([
'externalId' => 'user_42'
]),
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/games/{gameId}/assign"
payload := strings.NewReader("{\n \"externalId\": \"user_42\"\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/games/{gameId}/assign")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"user_42\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign")
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 \"externalId\": \"user_42\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"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,
"official": {
"id": "<string>",
"name": "<string>",
"email": "<string>",
"claimStatus": "confirmed"
},
"payment": {
"id": "<string>",
"positionId": "<string>",
"officialId": "<string>",
"amountCents": 123,
"amountDollars": 123,
"currency": "<string>",
"paidAt": "2023-11-07T05:31:56Z",
"failureCode": "<string>",
"failureMessage": "<string>",
"createdAt": "2023-11-07T05:31:56Z"
}
}
]
}
}{
"error": "invalid_json"
}{
"error": "unauthorized"
}{
"error": "league_not_found"
}{
"error": "position_taken"
}Assign an official to a position
The official must already be an approved league member (add via POST /leagues/{leagueId}/officials first) — else 409 not_a_league_official. Reference them by userId (wins if present) or externalId + source. Position: pass positionId explicitly, or omit it to claim the first open slot. Claims are race-safe — losing a concurrent claim returns 409 position_taken, which is safe to handle by picking another slot. Returns 200 (not 201). Emits position.claimed.
curl --request POST \
--url https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"externalId": "user_42"
}
'import requests
url = "https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign"
payload = { "externalId": "user_42" }
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({externalId: 'user_42'})
};
fetch('https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign', 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/games/{gameId}/assign",
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([
'externalId' => 'user_42'
]),
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/games/{gameId}/assign"
payload := strings.NewReader("{\n \"externalId\": \"user_42\"\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/games/{gameId}/assign")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"externalId\": \"user_42\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://whistle.huddleup-sports.com/api/v1/games/{gameId}/assign")
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 \"externalId\": \"user_42\"\n}"
response = http.request(request)
puts response.read_body{
"data": {
"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,
"official": {
"id": "<string>",
"name": "<string>",
"email": "<string>",
"claimStatus": "confirmed"
},
"payment": {
"id": "<string>",
"positionId": "<string>",
"officialId": "<string>",
"amountCents": 123,
"amountDollars": 123,
"currency": "<string>",
"paidAt": "2023-11-07T05:31:56Z",
"failureCode": "<string>",
"failureMessage": "<string>",
"createdAt": "2023-11-07T05:31:56Z"
}
}
]
}
}{
"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 game id (id from game responses).
Body
Whistle user id. Takes precedence.
Your id for the official. Identifies who to assign — does NOT make this call idempotent.
Derived from your key — omit, or echo your key's source; any other value returns 400 source_mismatch.
Optional; defaults to the first open position.
Response
Game detail after assignment
Same as Game but each position embeds official and payment. A position's official is independent of its status — a cancelled slot can retain its official (no-show record).
Show child attributes
Show child attributes

