Reserve stock for checkout
Optional. Holds stock for the items a customer is checking out so the units can’t be sold to someone else while they complete payment.
Use this when there is a gap between “customer commits to buy” and “payment
confirms” — card redirects, 3‑D Secure, or mobile‑money push prompts — so
the stock is claimed before the customer pays and a second shopper racing
for the last unit is turned away up front. For instant‑capture or fully
synchronous flows you can skip it and call POST /v1/orders directly;
Galactic Core still prevents stock from going negative at order time. In
other words, reserving is a stronger guarantee for async payments, not a
mandatory step before every order.
Reservations are all‑or‑nothing: if any item lacks available stock, nothing
is reserved and the response is 409. Each hold expires automatically after
a window (15 minutes by default). Pass the returned reservation_ids to
POST /v1/orders (or when you PATCH an order to paid) and Galactic Core
converts the hold into the actual stock deduction. If the customer abandons
checkout, the hold simply expires and the stock returns to availability —
you don’t need to release it explicitly.
Works with both publishable and secret keys (checkout originates in the browser).
Limits per request: with a publishable key, at most 25 items and 25 units
of any one variant (quantities for a repeated variant are added together);
with a secret key, at most 100 items and 1,000 units of any one variant. A
request over a limit is refused with 400 and nothing is reserved.
Rate limit: 300 requests/hour per API key, and with a publishable key 60 requests/hour per visitor.
A key issued through GC Connect needs the orders:write scope; without it
the request returns 403.
curl --request POST \
--url https://api.tybritelabs.com/v1/checkout/reserve \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"items": [
{
"variant_id": "0d65aa5b-082a-4847-810c-38ca18e0896d",
"quantity": 2
}
],
"ttl_seconds": 900
}
'import requests
url = "https://api.tybritelabs.com/v1/checkout/reserve"
payload = {
"items": [
{
"variant_id": "0d65aa5b-082a-4847-810c-38ca18e0896d",
"quantity": 2
}
],
"ttl_seconds": 900
}
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({
items: [{variant_id: '0d65aa5b-082a-4847-810c-38ca18e0896d', quantity: 2}],
ttl_seconds: 900
})
};
fetch('https://api.tybritelabs.com/v1/checkout/reserve', 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.tybritelabs.com/v1/checkout/reserve",
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([
'items' => [
[
'variant_id' => '0d65aa5b-082a-4847-810c-38ca18e0896d',
'quantity' => 2
]
],
'ttl_seconds' => 900
]),
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.tybritelabs.com/v1/checkout/reserve"
payload := strings.NewReader("{\n \"items\": [\n {\n \"variant_id\": \"0d65aa5b-082a-4847-810c-38ca18e0896d\",\n \"quantity\": 2\n }\n ],\n \"ttl_seconds\": 900\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.tybritelabs.com/v1/checkout/reserve")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"items\": [\n {\n \"variant_id\": \"0d65aa5b-082a-4847-810c-38ca18e0896d\",\n \"quantity\": 2\n }\n ],\n \"ttl_seconds\": 900\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/checkout/reserve")
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 \"items\": [\n {\n \"variant_id\": \"0d65aa5b-082a-4847-810c-38ca18e0896d\",\n \"quantity\": 2\n }\n ],\n \"ttl_seconds\": 900\n}"
response = http.request(request)
puts response.read_body{
"reservations": [
{
"reservation_id": "9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"variant_id": "0d65aa5b-082a-4847-810c-38ca18e0896d",
"quantity": 2
}
],
"reservation_ids": [
"9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"
],
"expires_in_seconds": 900
}{
"error": {
"code": "invalid_request",
"message": "Invalid request parameters",
"details": "The 'email' field is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing or invalid Authorization header"
}
}{
"error": {
"code": "forbidden",
"message": "This operation requires a secret key. Publishable keys cover catalogue reads and the actions a shopper takes for themselves."
}
}{
"error": {
"code": "insufficient_stock",
"message": "Insufficient stock: 1 available, 2 requested",
"details": "variant 0d65aa5b-082a-4847-810c-38ca18e0896d: 1 available, 2 requested"
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key. Please slow down and try again shortly.",
"remaining": 0,
"reset": 1706198400
}
}{
"error": {
"code": "server_error",
"message": "An unexpected error occurred"
}
}Authorizations
API Key Authentication
Use your API key in the Authorization header:
Authorization: Bearer tybrite_sk_live_YOUR_KEY
Key Types:
Secret Keys (Server-Side Only):
- Format:
tybrite_sk_live_*(production) ortybrite_sk_test_*(sandbox) - Full read/write access to all endpoints
- ⚠️ NEVER expose in client-side code or public repositories
- Required for: every write a shopper does not make for themselves, authentication, payment verification, AI recommendations
Publishable Keys (Client-Safe):
- Format:
tybrite_pk_live_*(production) ortybrite_pk_test_*(sandbox) - Catalogue reads, plus the actions a shopper takes for themselves
- ✅ Safe for client-side JavaScript, mobile apps, and public code
- Allowed for: browsing products, search, CMS content, pricing queries, cart and wishlist, reviews, storefront events, and the Agent API's quotes, cart drafts and checkout intents
Endpoint-Specific Requirements:
- Authentication endpoints (
/v1/auth/*): Secret key required - Payment verification (
POST /v1/payments/verify): Secret key required - AI Recommendations (
POST /v1/recommendations): Secret key required - Semantic Search (
POST /v1/search): Both key types allowed (read-only operation) - Shopper actions (cart, wishlist, reviews, storefront events, Agent API quotes, cart drafts and checkout intents): Both key types allowed
- All other write operations: Secret key required
- All read operations: Both key types allowed
Using a publishable key for restricted operations returns 403 Forbidden.
Body
The variants and quantities to hold.
1Show child attributes
Show child attributes
Optional hold duration in seconds. Defaults to 900 (15 minutes); values below 60 are raised to 60.
900
Optional - associate the hold with a customer (used for checkout recovery). Honoured only with a secret key; with a publishable key the hold is recorded without a customer.
Optional - associate the hold with an anonymous browser session.
curl --request POST \
--url https://api.tybritelabs.com/v1/checkout/reserve \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"items": [
{
"variant_id": "0d65aa5b-082a-4847-810c-38ca18e0896d",
"quantity": 2
}
],
"ttl_seconds": 900
}
'import requests
url = "https://api.tybritelabs.com/v1/checkout/reserve"
payload = {
"items": [
{
"variant_id": "0d65aa5b-082a-4847-810c-38ca18e0896d",
"quantity": 2
}
],
"ttl_seconds": 900
}
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({
items: [{variant_id: '0d65aa5b-082a-4847-810c-38ca18e0896d', quantity: 2}],
ttl_seconds: 900
})
};
fetch('https://api.tybritelabs.com/v1/checkout/reserve', 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.tybritelabs.com/v1/checkout/reserve",
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([
'items' => [
[
'variant_id' => '0d65aa5b-082a-4847-810c-38ca18e0896d',
'quantity' => 2
]
],
'ttl_seconds' => 900
]),
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.tybritelabs.com/v1/checkout/reserve"
payload := strings.NewReader("{\n \"items\": [\n {\n \"variant_id\": \"0d65aa5b-082a-4847-810c-38ca18e0896d\",\n \"quantity\": 2\n }\n ],\n \"ttl_seconds\": 900\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.tybritelabs.com/v1/checkout/reserve")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"items\": [\n {\n \"variant_id\": \"0d65aa5b-082a-4847-810c-38ca18e0896d\",\n \"quantity\": 2\n }\n ],\n \"ttl_seconds\": 900\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/checkout/reserve")
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 \"items\": [\n {\n \"variant_id\": \"0d65aa5b-082a-4847-810c-38ca18e0896d\",\n \"quantity\": 2\n }\n ],\n \"ttl_seconds\": 900\n}"
response = http.request(request)
puts response.read_body{
"reservations": [
{
"reservation_id": "9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
"variant_id": "0d65aa5b-082a-4847-810c-38ca18e0896d",
"quantity": 2
}
],
"reservation_ids": [
"9f1b2c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d"
],
"expires_in_seconds": 900
}{
"error": {
"code": "invalid_request",
"message": "Invalid request parameters",
"details": "The 'email' field is required"
}
}{
"error": {
"code": "unauthorized",
"message": "Missing or invalid Authorization header"
}
}{
"error": {
"code": "forbidden",
"message": "This operation requires a secret key. Publishable keys cover catalogue reads and the actions a shopper takes for themselves."
}
}{
"error": {
"code": "insufficient_stock",
"message": "Insufficient stock: 1 available, 2 requested",
"details": "variant 0d65aa5b-082a-4847-810c-38ca18e0896d: 1 available, 2 requested"
}
}{
"error": {
"code": "rate_limited",
"message": "Too many requests for this API key. Please slow down and try again shortly.",
"remaining": 0,
"reset": 1706198400
}
}{
"error": {
"code": "server_error",
"message": "An unexpected error occurred"
}
}
