Subscribe & Save offers on a product
The Subscribe & Save offers that cover a product: each offer’s label, the delivery intervals it allows, its discount, any different discount on the first delivery, the minimum number of deliveries, the yearly skip limit and whether shipping is free. The list is empty when the store offers none.
With variant_id, each offer also carries per_delivery: the price of one delivery of that variant
(times quantity) for the first delivery and for every later one, worked out from the variant’s live
catalogue price. These prices cover the items only; shipping and tax are added at checkout. Without
variant_id, per_delivery is null.
A subscription is started with POST /v1/agent/me/shopper-subscriptions.
curl --request GET \
--url https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save', 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/agent/products/{id}/subscribe-save",
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: Bearer <token>"
],
]);
$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.tybritelabs.com/v1/agent/products/{id}/subscribe-save"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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.tybritelabs.com/v1/agent/products/{id}/subscribe-save")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": {
"product_id": "289533e8-f5b6-4d4f-bb23-2127875feb70",
"variant_id": "fbe89cdf-90a3-4050-a43b-e4c42302484a",
"offers": [
{
"id": "4c1c7b9e-2f58-4f43-9d3a-6f0e2a8b1c11",
"label": "Subscribe & save 15%",
"intervals": [
{
"unit": "month",
"count": 1
},
{
"unit": "week",
"count": 6
}
],
"discount_type": "percentage",
"discount_value": 1500,
"first_order_discount_value": 2000,
"min_cycles": 3,
"max_skips_per_year": 2,
"free_shipping": false,
"stacks_with_promotions": false,
"image": null,
"image_mobile": null,
"video": null,
"ends_at": null,
"per_delivery": {
"currency": "USD",
"quantity": 1,
"unit_price": 24,
"first_delivery": {
"price": 19.2,
"discount": 4.8
},
"later_delivery": {
"price": 20.4,
"discount": 3.6
},
"free_shipping": false
}
}
]
},
"evidence": [
{
"source": "subscribe_save",
"field": "offers"
},
{
"source": "pricing",
"operation": "GET /v1/prices/products/{id}",
"id": "fbe89cdf-90a3-4050-a43b-e4c42302484a",
"field": "resolved_price"
},
{
"source": "subscribe_save",
"field": "per_delivery"
}
],
"computed_at": "2026-10-28T11:25:08.030Z",
"currency": "USD",
"environment": "production"
}{
"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": "not_found",
"message": "Resource not found"
}
}{
"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.
Path Parameters
The product.
Query Parameters
A variant of the product. When given, per_delivery is filled in.
Units of the variant in each delivery.
1 <= x <= 999curl --request GET \
--url https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save', 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/agent/products/{id}/subscribe-save",
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: Bearer <token>"
],
]);
$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.tybritelabs.com/v1/agent/products/{id}/subscribe-save"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
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.tybritelabs.com/v1/agent/products/{id}/subscribe-save")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/agent/products/{id}/subscribe-save")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body{
"data": {
"product_id": "289533e8-f5b6-4d4f-bb23-2127875feb70",
"variant_id": "fbe89cdf-90a3-4050-a43b-e4c42302484a",
"offers": [
{
"id": "4c1c7b9e-2f58-4f43-9d3a-6f0e2a8b1c11",
"label": "Subscribe & save 15%",
"intervals": [
{
"unit": "month",
"count": 1
},
{
"unit": "week",
"count": 6
}
],
"discount_type": "percentage",
"discount_value": 1500,
"first_order_discount_value": 2000,
"min_cycles": 3,
"max_skips_per_year": 2,
"free_shipping": false,
"stacks_with_promotions": false,
"image": null,
"image_mobile": null,
"video": null,
"ends_at": null,
"per_delivery": {
"currency": "USD",
"quantity": 1,
"unit_price": 24,
"first_delivery": {
"price": 19.2,
"discount": 4.8
},
"later_delivery": {
"price": 20.4,
"discount": 3.6
},
"free_shipping": false
}
}
]
},
"evidence": [
{
"source": "subscribe_save",
"field": "offers"
},
{
"source": "pricing",
"operation": "GET /v1/prices/products/{id}",
"id": "fbe89cdf-90a3-4050-a43b-e4c42302484a",
"field": "resolved_price"
},
{
"source": "subscribe_save",
"field": "per_delivery"
}
],
"computed_at": "2026-10-28T11:25:08.030Z",
"currency": "USD",
"environment": "production"
}{
"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": "not_found",
"message": "Resource not found"
}
}{
"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"
}
}
