Create a sandbox promotion
Creates a promotion in your sandbox so you can apply a discount to a sandbox order.
Promotions are environment-scoped: a test key resolves only sandbox promotions and a live key only real ones. Because promotions are otherwise created by the merchant in their admin — which always writes production — this endpoint is how a promotion comes to exist in your sandbox at all.
Every field is optional; the defaults produce an active percentage promotion that starts today
and runs for 30 days. The environment is always set to sandbox by Galactic Core and is never
read from the request body.
Requires a secret test key (tybrite_sk_test_*). Publishable keys and live keys are
rejected.
curl --request POST \
--url https://api.tybritelabs.com/v1/sandbox/seed/promotion \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Launch week",
"type": "percentage",
"value": 15,
"min_purchase": 50
}
'import requests
url = "https://api.tybritelabs.com/v1/sandbox/seed/promotion"
payload = {
"name": "Launch week",
"type": "percentage",
"value": 15,
"min_purchase": 50
}
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({name: 'Launch week', type: 'percentage', value: 15, min_purchase: 50})
};
fetch('https://api.tybritelabs.com/v1/sandbox/seed/promotion', 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/sandbox/seed/promotion",
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([
'name' => 'Launch week',
'type' => 'percentage',
'value' => 15,
'min_purchase' => 50
]),
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/sandbox/seed/promotion"
payload := strings.NewReader("{\n \"name\": \"Launch week\",\n \"type\": \"percentage\",\n \"value\": 15,\n \"min_purchase\": 50\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/sandbox/seed/promotion")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Launch week\",\n \"type\": \"percentage\",\n \"value\": 15,\n \"min_purchase\": 50\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/sandbox/seed/promotion")
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 \"name\": \"Launch week\",\n \"type\": \"percentage\",\n \"value\": 15,\n \"min_purchase\": 50\n}"
response = http.request(request)
puts response.read_body{
"promotion": {
"id": "583bcfa6-a86a-4bc4-87e0-1a70e6294bda",
"name": "Launch week",
"type": "percentage",
"value": "15",
"min_purchase": 50,
"start_date": "2026-09-07",
"end_date": "2026-10-07",
"status": "active",
"environment": "sandbox"
}
}{
"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": "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
Display name. Defaults to a generated Sandbox promotion … name.
percentage and fixed discount the whole cart. bogo and bundle carry product
lists and are configured by the merchant.
percentage, fixed, bogo, bundle Percentage points for percentage, else a cash amount.
Cart subtotal required before the promotion applies.
Defaults to today. Cannot be in the past.
Defaults to 30 days from today.
Response
Sandbox promotion created.
A promotion created in the sandbox. Only ever resolved by a test key.
Show child attributes
Show child attributes
curl --request POST \
--url https://api.tybritelabs.com/v1/sandbox/seed/promotion \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"name": "Launch week",
"type": "percentage",
"value": 15,
"min_purchase": 50
}
'import requests
url = "https://api.tybritelabs.com/v1/sandbox/seed/promotion"
payload = {
"name": "Launch week",
"type": "percentage",
"value": 15,
"min_purchase": 50
}
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({name: 'Launch week', type: 'percentage', value: 15, min_purchase: 50})
};
fetch('https://api.tybritelabs.com/v1/sandbox/seed/promotion', 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/sandbox/seed/promotion",
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([
'name' => 'Launch week',
'type' => 'percentage',
'value' => 15,
'min_purchase' => 50
]),
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/sandbox/seed/promotion"
payload := strings.NewReader("{\n \"name\": \"Launch week\",\n \"type\": \"percentage\",\n \"value\": 15,\n \"min_purchase\": 50\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/sandbox/seed/promotion")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"name\": \"Launch week\",\n \"type\": \"percentage\",\n \"value\": 15,\n \"min_purchase\": 50\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/sandbox/seed/promotion")
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 \"name\": \"Launch week\",\n \"type\": \"percentage\",\n \"value\": 15,\n \"min_purchase\": 50\n}"
response = http.request(request)
puts response.read_body{
"promotion": {
"id": "583bcfa6-a86a-4bc4-87e0-1a70e6294bda",
"name": "Launch week",
"type": "percentage",
"value": "15",
"min_purchase": 50,
"start_date": "2026-09-07",
"end_date": "2026-10-07",
"status": "active",
"environment": "sandbox"
}
}{
"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": "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"
}
}
