curl --request GET \
--url https://api.4casters.io/user/v2/getGradedSince \
--header 'Authorization: <api-key>'import requests
url = "https://api.4casters.io/user/v2/getGradedSince"
headers = {"Authorization": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: '<api-key>'}};
fetch('https://api.4casters.io/user/v2/getGradedSince', 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://api.4casters.io/user/v2/getGradedSince",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: <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://api.4casters.io/user/v2/getGradedSince"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "<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://api.4casters.io/user/v2/getGradedSince")
.header("Authorization", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.4casters.io/user/v2/getGradedSince")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": {
"graded": [
{
"game": {
"isSpecials": true,
"tournamentName": "<string>",
"periodName": "<string>",
"id": "<string>",
"league": "<string>",
"live": true,
"sport": "<string>",
"participants": [
{
"id": "<string>",
"longName": "<string>",
"shortName": "<string>",
"homeAway": "home",
"futuresSide": "<string>",
"mainPitcher": "<string>",
"score": 123,
"rotationNumber": "<string>"
}
],
"eventName": "<string>",
"ended": true,
"parentGameID": "<string>",
"start": "2026-08-01T00:00:00.000Z"
},
"id": "<string>",
"type": "moneyline",
"bet": 123,
"txID": "<string>",
"closed": false,
"ticketNumber": "<string>",
"odds": 123,
"fee": "<string>",
"risk": "<string>",
"win": "<string>",
"origin": "wager",
"cancelled": false,
"outcome": "winner",
"graded": true,
"createdAt": "2026-08-01T00:00:00.000Z",
"adminRefund": true,
"wagerRequestID": "<string>",
"userReference": "<string>",
"isPostArb": true,
"matchedTime": "2026-08-01T00:00:00.000Z",
"settledAt": "2026-08-01T00:00:00.000Z",
"platform": "<string>",
"result": "<string>",
"participantID": "<string>",
"spread": 123,
"total": 123,
"OU": "over",
"side": "yes",
"market": "<string>"
}
],
"hasMore": true,
"cursor": "<string>",
"settledThrough": "2026-08-01T00:00:00.000Z"
}
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Get graded bets since a cursor
Your settled (graded) bets in settlement order, paged by an opaque cursor — the way to keep a local copy of your bet history in sync without guessing date windows.
First call: send since (ISO 8601). You get the bets settled strictly after that instant, oldest first, plus a cursor.
Every call after that — the recommended loop: poll once per second with your latest cursor. If a response has hasMore: true, there is more to fetch — call again immediately with the new cursor until hasMore is false, then go back to polling once per second. Never construct or modify a cursor; a cursor you did not receive from this endpoint is rejected with 400. An empty page returns your cursor unchanged, so a poller never loses its place.
let cursor = firstResponse.data.cursor; // from your one-time ?since=<ISO> call
while (true) {
let hasMore = true;
while (hasMore) {
const res = await get('/user/v2/getGradedSince', { cursor });
if (res.status === 429) { await sleep(res.headers['retry-after'] * 1000); continue; } // same cursor
for (const row of res.data.graded) upsert(row.txID, row);
cursor = res.data.cursor;
hasMore = res.data.hasMore;
}
await sleep(1000);
}
Rows are exactly getGraded’s rows (per fill, your side, game embedded). A bet can appear again later if its settlement changes — a refund, or a regrade after a score correction — with a newer position in the walk; treat txID as the row’s identity and let the latest version win.
Pages are a fixed 100 rows, and the endpoint is rate limited to 10 requests per 5 seconds per user. That leaves room to drain a burst while polling once per second; live settlement never fills a page (a few rows per second at most), so hasMore effectively only fires when you are catching up after downtime — a day of a busy account’s settlements drains in about 12 seconds. If you do get a 429, wait Retry-After seconds and resend the same cursor. One second is the recommended and, in practice, the maximum polling rate.
settledThrough — the human-readable position. Every response also carries settledThrough, an ISO timestamp meaning you now hold every bet settled at or before this instant. Several bets can settle in the same millisecond, and a page can end inside such a group; settledThrough then steps back to the last instant that is complete, so it is always safe. If you would rather not store an opaque token, resume with since=settledThrough: you can never miss a bet that way, you may just see the rows of that last group again (dedupe by txID). The cursor remains the exact position
Also accepts POST with the same parameters sent as a JSON body.
curl --request GET \
--url https://api.4casters.io/user/v2/getGradedSince \
--header 'Authorization: <api-key>'import requests
url = "https://api.4casters.io/user/v2/getGradedSince"
headers = {"Authorization": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: '<api-key>'}};
fetch('https://api.4casters.io/user/v2/getGradedSince', 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://api.4casters.io/user/v2/getGradedSince",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: <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://api.4casters.io/user/v2/getGradedSince"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "<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://api.4casters.io/user/v2/getGradedSince")
.header("Authorization", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.4casters.io/user/v2/getGradedSince")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"data": {
"graded": [
{
"game": {
"isSpecials": true,
"tournamentName": "<string>",
"periodName": "<string>",
"id": "<string>",
"league": "<string>",
"live": true,
"sport": "<string>",
"participants": [
{
"id": "<string>",
"longName": "<string>",
"shortName": "<string>",
"homeAway": "home",
"futuresSide": "<string>",
"mainPitcher": "<string>",
"score": 123,
"rotationNumber": "<string>"
}
],
"eventName": "<string>",
"ended": true,
"parentGameID": "<string>",
"start": "2026-08-01T00:00:00.000Z"
},
"id": "<string>",
"type": "moneyline",
"bet": 123,
"txID": "<string>",
"closed": false,
"ticketNumber": "<string>",
"odds": 123,
"fee": "<string>",
"risk": "<string>",
"win": "<string>",
"origin": "wager",
"cancelled": false,
"outcome": "winner",
"graded": true,
"createdAt": "2026-08-01T00:00:00.000Z",
"adminRefund": true,
"wagerRequestID": "<string>",
"userReference": "<string>",
"isPostArb": true,
"matchedTime": "2026-08-01T00:00:00.000Z",
"settledAt": "2026-08-01T00:00:00.000Z",
"platform": "<string>",
"result": "<string>",
"participantID": "<string>",
"spread": 123,
"total": 123,
"OU": "over",
"side": "yes",
"market": "<string>"
}
],
"hasMore": true,
"cursor": "<string>",
"settledThrough": "2026-08-01T00:00:00.000Z"
}
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Authorizations
The auth token returned by POST /user/login (data.user.auth), sent as the raw header value — no Bearer prefix.
Query Parameters
First call: return bets settled strictly after this instant. Mutually exclusive with cursor.
"2026-08-01T00:00:00.000Z"
Subsequent calls: the cursor from the previous response, verbatim. Mutually exclusive with since.
Response
OK
Show child attributes
Show child attributes
Was this page helpful?