Place orders
curl --request POST \
--url https://api.4casters.io/session/v3/place \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"orders": [
{
"gameID": "<string>",
"side": "<string>",
"odds": 123,
"bet": 123,
"market": "<string>",
"number": 123,
"expirationMinutes": 123,
"userReference": "<string>"
}
]
}
'import requests
url = "https://api.4casters.io/session/v3/place"
payload = { "orders": [
{
"gameID": "<string>",
"side": "<string>",
"odds": 123,
"bet": 123,
"market": "<string>",
"number": 123,
"expirationMinutes": 123,
"userReference": "<string>"
}
] }
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({
orders: [
{
gameID: '<string>',
side: '<string>',
odds: 123,
bet: 123,
market: '<string>',
number: 123,
expirationMinutes: 123,
userReference: '<string>'
}
]
})
};
fetch('https://api.4casters.io/session/v3/place', 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/session/v3/place",
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([
'orders' => [
[
'gameID' => '<string>',
'side' => '<string>',
'odds' => 123,
'bet' => 123,
'market' => '<string>',
'number' => 123,
'expirationMinutes' => 123,
'userReference' => '<string>'
]
]
]),
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://api.4casters.io/session/v3/place"
payload := strings.NewReader("{\n \"orders\": [\n {\n \"gameID\": \"<string>\",\n \"side\": \"<string>\",\n \"odds\": 123,\n \"bet\": 123,\n \"market\": \"<string>\",\n \"number\": 123,\n \"expirationMinutes\": 123,\n \"userReference\": \"<string>\"\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://api.4casters.io/session/v3/place")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"orders\": [\n {\n \"gameID\": \"<string>\",\n \"side\": \"<string>\",\n \"odds\": 123,\n \"bet\": 123,\n \"market\": \"<string>\",\n \"number\": 123,\n \"expirationMinutes\": 123,\n \"userReference\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.4casters.io/session/v3/place")
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 \"orders\": [\n {\n \"gameID\": \"<string>\",\n \"side\": \"<string>\",\n \"odds\": 123,\n \"bet\": 123,\n \"market\": \"<string>\",\n \"number\": 123,\n \"expirationMinutes\": 123,\n \"userReference\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"createdSessions": [
{
"matched": [
{
"amount": 123,
"odds": 123,
"number": 123,
"type": "moneyline",
"side": "<string>",
"market": "<string>",
"orderID": "<string>",
"txID": "<string>",
"wagerRequestID": "<string>",
"userReference": "<string>",
"risk": 123,
"win": 123,
"winWithoutCommission": 123
}
],
"unmatched": {
"orderID": "<string>",
"wagerRequestID": "<string>",
"offered": 123,
"odds": 123,
"type": "moneyline",
"side": "<string>",
"market": "<string>",
"number": 123,
"userReference": "<string>"
}
}
]
}
}Orders
Place orders
Submit one or more orders to the exchange
POST
/
session
/
v3
/
place
Place orders
curl --request POST \
--url https://api.4casters.io/session/v3/place \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"orders": [
{
"gameID": "<string>",
"side": "<string>",
"odds": 123,
"bet": 123,
"market": "<string>",
"number": 123,
"expirationMinutes": 123,
"userReference": "<string>"
}
]
}
'import requests
url = "https://api.4casters.io/session/v3/place"
payload = { "orders": [
{
"gameID": "<string>",
"side": "<string>",
"odds": 123,
"bet": 123,
"market": "<string>",
"number": 123,
"expirationMinutes": 123,
"userReference": "<string>"
}
] }
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({
orders: [
{
gameID: '<string>',
side: '<string>',
odds: 123,
bet: 123,
market: '<string>',
number: 123,
expirationMinutes: 123,
userReference: '<string>'
}
]
})
};
fetch('https://api.4casters.io/session/v3/place', 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/session/v3/place",
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([
'orders' => [
[
'gameID' => '<string>',
'side' => '<string>',
'odds' => 123,
'bet' => 123,
'market' => '<string>',
'number' => 123,
'expirationMinutes' => 123,
'userReference' => '<string>'
]
]
]),
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://api.4casters.io/session/v3/place"
payload := strings.NewReader("{\n \"orders\": [\n {\n \"gameID\": \"<string>\",\n \"side\": \"<string>\",\n \"odds\": 123,\n \"bet\": 123,\n \"market\": \"<string>\",\n \"number\": 123,\n \"expirationMinutes\": 123,\n \"userReference\": \"<string>\"\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://api.4casters.io/session/v3/place")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"orders\": [\n {\n \"gameID\": \"<string>\",\n \"side\": \"<string>\",\n \"odds\": 123,\n \"bet\": 123,\n \"market\": \"<string>\",\n \"number\": 123,\n \"expirationMinutes\": 123,\n \"userReference\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.4casters.io/session/v3/place")
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 \"orders\": [\n {\n \"gameID\": \"<string>\",\n \"side\": \"<string>\",\n \"odds\": 123,\n \"bet\": 123,\n \"market\": \"<string>\",\n \"number\": 123,\n \"expirationMinutes\": 123,\n \"userReference\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"createdSessions": [
{
"matched": [
{
"amount": 123,
"odds": 123,
"number": 123,
"type": "moneyline",
"side": "<string>",
"market": "<string>",
"orderID": "<string>",
"txID": "<string>",
"wagerRequestID": "<string>",
"userReference": "<string>",
"risk": 123,
"win": 123,
"winWithoutCommission": 123
}
],
"unmatched": {
"orderID": "<string>",
"wagerRequestID": "<string>",
"offered": 123,
"odds": 123,
"type": "moneyline",
"side": "<string>",
"market": "<string>",
"number": 123,
"userReference": "<string>"
}
}
]
}
}Submit one or more orders. The endpoint accepts a batch — per-order results come back in
The default order type. Executes against any matching liquidity at your price or better as a taker (with taker commission), and rests any remainder on the book as a maker. A
Create a resting offer. If the order would match existing liquidity when placed, the server rejects it (
Behaves like
Execute immediately as a taker against whatever size is available at your price or better, and cancel any remainder. A
All-or-nothing. Your entire
With no matchable liquidity at all, the same order is rejected with Scenario 6 —
Scenario 7 —
To bet that the home team does not win, send
data.createdSessions, positionally with your input orders array. Each entry is either a successful { matched, unmatched } result or an { error, errorType } failure.
Request
POST /session/v3/place
curl -X POST https://api.4casters.io/session/v3/place \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"orders": [
{
"gameID": "688c0516fbc14da0c202d426",
"type": "moneyline",
"side": "5c12bc1ce0daba000f47ba8b",
"odds": -175,
"bet": 100,
"orderType": "post",
"userReference": "docs-example"
}
]
}'
{
"orders": [
{
"gameID": "4CASTER_GAME_ID",
"type": "moneyline | spread | total | moneyline1x2",
"side": "PARTICIPANT_ID | over | under | yes | no",
"market": "PARTICIPANT_ID | draw",
"odds": -110,
"bet": 100,
"number": 3.5,
"orderType": "limit | post | postArb | fillAndKill | fillOrKill",
"expirationMinutes": 10,
"userReference": "OPTIONAL_CLIENT_SIDE_IDENTIFIER"
}
]
}
Order fields
Order types
limit
The default order type. Executes against any matching liquidity at your price or better as a taker (with taker commission), and rests any remainder on the book as a maker. A limit order can finish fully matched, fully resting, or partially matched with the remainder resting.
post
Create a resting offer. If the order would match existing liquidity when placed, the server rejects it (rejected_order_type_rules, e.g. "post order cannot have matches").
postArb
Behaves like post, but you may post even when the order would match, only if your American odds are within 1% of the odds on the resting order you would match. If the price difference is more than 1%, the place is rejected.
Examples:
- Best offer +100 and you post +100 —
postis rejected (it would match);postArbis allowed. - Best offer +200 —
postArbat +190 is rejected (too far from +200). About +198 is at the edge of the 1% band vs +200. - Best order −200 — the band extends to about −202 on the negative side (same 1% rule).
- With +100 on the book, −101 is the reference limit on the other side for the 1% tolerance.
postArb avoids taker fees across a normal trade — you are not charged taker fees on this flow the way you would be if you lifted resting liquidity as a taker.
Orders placed as postArb are flagged with isPostArb: true on their user feed and price feed updates; the field is omitted for other order types. The place response itself does not include the flag.
fillAndKill
Execute immediately as a taker against whatever size is available at your price or better, and cancel any remainder. A fillAndKill never rests on the book.
A partial fill is a successful outcome: if you send bet: 1000 and only 400ofmatchableliquidityexists,youarefilledfor400 and the remaining $600 is cancelled — the response is a success, not an error. The order is rejected (rejected_order_type_rules, "fill and kill has no matches") only when there is no matchable liquidity at all. See the Fill And Kill scenarios in the examples below.
fillOrKill
All-or-nothing. Your entire bet must match immediately at your price or better, otherwise the whole order is rejected — including any fills it made along the way, which are rolled back. A fillOrKill never rests on the book, and you are never left partially filled.
Use it when a partial position is worse than no position, for example when the order is one leg of a hedge you can only execute in full.
Rejections carry errorType: rejected_order_type_rules with one of two messages, which distinguish “nothing was there” from “not enough was there”:
"fill or kill has no matches"— no matchable liquidity at your price."fill or kill matched but not fully"— some liquidity matched, but not your full size. Those fills were rolled back.
A residual of **10orless∗∗countsascomplete:a‘fillOrKill‘for1,000 that matches 992succeeds,becausethe8 that could not be filled is below the minimum size that could ever rest on the book. A residual larger than $10 rejects the order.
Response
array
Per-order results, positional with the input
orders array. Each entry is either a successful place or an error.Show Successful place
Show Successful place
array
Matched fills produced by this order. Empty when the order did not match.
Show MatchedFill
Show MatchedFill
number
Stake on the matched portion.
integer
American odds of the fill.
number
Spread or total of the fill (
null for moneylines).string
Market type.
string
Order side.
string
Present for
moneyline1x2.string
Order id of the matched offer on the other side.
string
Transaction id of the fill.
string
Server-generated id grouping every fill / offer derived from this input order.
string
number
number
Win amount, net of taker commission.
number
Win amount before commission.
Examples
Scenario 1 — Match orders with no leftover liquidity
Three orders that all match instantly with available liquidity.{
"orders": [
{ "gameID": "688c0516fbc14da0c202d426", "type": "moneyline", "side": "5c12bc1ce0daba000f47ba8b", "odds": -175, "bet": 100, "orderType": "post" },
{ "gameID": "688c0516fbc14da0c202d426", "type": "spread", "side": "5c12bc1ce0daba000f47ba8b", "odds": -110, "bet": 100, "orderType": "post", "number": 3.5 },
{ "gameID": "688c0516fbc14da0c202d426", "type": "total", "side": "over", "odds": -104, "bet": 100, "orderType": "post", "number": 50 }
]
}
{
"data": {
"createdSessions": [
{
"matched": [
{
"amount": 50, "odds": -175, "type": "moneyline",
"side": "5d48bd5198366d41ec7238da",
"orderID": "68d42f82cfebf0b249a2c26e",
"txID": "68d42f83cfebf0b249a2c279",
"wagerRequestID": "68d42f83cfebf0b249a2c278",
"risk": 50.286, "win": 28.286, "winWithoutCommission": 28.571
}
],
"unmatched": {}
},
{ "matched": [/* spread fill */], "unmatched": {} },
{ "matched": [/* total fill */], "unmatched": {} }
]
}
}
Scenario 2 — Limit order with leftover liquidity
A limit order for 300 that partially matches and leaves the remainder resting.{
"data": {
"createdSessions": [
{
"matched": [
{
"amount": 185, "odds": -185, "type": "moneyline",
"side": "5d48bd5198366d41ec7238da",
"orderID": "68d43574cfebf0b249a2c28d",
"txID": "68d43574cfebf0b249a2c290",
"wagerRequestID": "68d43574cfebf0b249a2c28f",
"risk": 186, "win": 99, "winWithoutCommission": 100
}
],
"unmatched": {
"orderID": "68d43575cfebf0b249a2c292",
"wagerRequestID": "68d43574cfebf0b249a2c28f",
"offered": 115, "odds": -185, "type": "moneyline",
"side": "5d48bd5198366d41ec7238da", "number": null
}
}
]
}
}
Scenario 3 — Fill and Kill, full match
{
"data": {
"createdSessions": [
{
"matched": [
{
"amount": 100, "odds": -186, "type": "moneyline",
"side": "5d48bd5198366d41ec7238da",
"orderID": "68d4368acfebf0b249a2c298",
"txID": "68d436bacfebf0b249a2c29b",
"wagerRequestID": "68d436bacfebf0b249a2c29a",
"userReference": "docs-fillandkill-match",
"risk": 100.538, "win": 53.226, "winWithoutCommission": 53.763
}
],
"unmatched": {}
}
]
}
}
Scenario 4 — Fill and Kill, no match
AfillAndKill with no match returns a per-order error.
{
"data": {
"createdSessions": [
{
"error": "fill and kill has no matches",
"errorType": "rejected_order_type_rules"
}
]
}
}
Scenario 5 — Fill or Kill, not enough liquidity
fillOrKill is the all-or-nothing counterpart to fillAndKill. Where a fillAndKill for 1,000against400 of liquidity fills 400andcancelstherest,a‘fillOrKill‘rejectsthewholeorderandrollsbackthat400 — you are never left partially filled.
{
"orders": [
{
"gameID": "688c0516fbc14da0c202d426",
"type": "moneyline",
"side": "5c12bc1ce0daba000f47ba8b",
"odds": -110,
"bet": 1000,
"orderType": "fillOrKill",
"userReference": "docs-fillorkill-partial"
}
]
}
{
"data": {
"createdSessions": [
{
"error": "fill or kill matched but not fully",
"errorType": "rejected_order_type_rules"
}
]
}
}
"fill or kill has no matches" instead.
Scenario 6 — postArb vs post when the book would match
post rejects an order that would match resting liquidity (for example, best offer +100 and you try to post +100). postArb allows that situation when your odds are within 1% of the order you would match — so the same +100 post can succeed as postArb, and you avoid the taker fees you’d pay across a normal trade. If your price is too far from the resting quote (e.g. best offer +200 but you send +190), postArb is rejected.
{
"orders": [
{
"gameID": "688c0516fbc14da0c202d426",
"type": "moneyline",
"side": "5c12bc1ce0daba000f47ba8b",
"odds": 100,
"bet": 50,
"orderType": "postArb",
"userReference": "docs-postarb-same-line-as-offer"
}
]
}
Scenario 7 — moneyline1x2 (soccer three-way)
moneyline1x2 is the soccer three-way market — home / away / draw — placed as a yes/no bet on the outcome named by market. Below, yes on the draw at +250.
{
"orders": [
{
"gameID": "65f0c3...",
"type": "moneyline1x2",
"side": "yes",
"market": "draw",
"odds": 250,
"bet": 50,
"orderType": "post",
"userReference": "docs-ml1x2-yes-draw"
}
]
}
side: "no" and set market to the home participant’s id.
Live delay
Orders placed on a game marked as live follow these rules:- If the order does not match any existing liquidity, it is placed immediately.
- If the order does match existing liquidity, it incurs a delay before execution.
- Different leagues have different live delay periods:
- NFL, UFCMMA, NCAAF — 3 seconds.
- NCAAB, NBA — 5 seconds.
- ATP, WTA — 8 seconds.
- Default — 10 seconds.
- After the delay, the order attempts to execute:
- If the odds improve, it matches instantly.
- If the odds decrease, it does not match.
Per-order errors
Example with multiple places, some erroring:{
"data": {
"createdSessions": [
{ "matched": [/* successful fill */], "unmatched": {} },
{ "error": "game not found: invalid gameID", "errorType": "validation_error" },
{ "error": "Insufficient balance.", "errorType": "rejected_liability" },
{ "error": "post order cannot have matches", "errorType": "rejected_order_type_rules" },
{ "error": "failed to interact with database","errorType": "system_error" }
]
}
}
Authorizations
Pass your auth token in the Authorization header. The Bearer prefix is optional; the server also accepts a signed auth cookie or a token field in the request body.
Body
application/json
Show child attributes
Show child attributes
Response
Per-order results (positional with input).
Show child attributes
Show child attributes
Was this page helpful?