Change a Subscribe & Save subscription
Changes how often one of the shopper’s subscriptions delivers, or what it delivers. Send either
interval_unit with interval_count (one of the intervals the offer allows), or items, the full new
list of products and quantities (each covered by the offer). The next delivery date is recalculated from
the last delivery. A store can turn off item changes for shoppers, in which case items answers
409. Requires the shopper’s credential and personalization consent.
curl --request POST \
--url https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"interval_count": 26,
"items": [
{
"product_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"quantity": 500,
"variant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]
}
'import requests
url = "https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update"
payload = {
"interval_count": 26,
"items": [
{
"product_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"quantity": 500,
"variant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]
}
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({
interval_count: 26,
items: [
{
product_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
quantity: 500,
variant_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
}
]
})
};
fetch('https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update', 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/me/shopper-subscriptions/{id}/update",
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([
'interval_count' => 26,
'items' => [
[
'product_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'quantity' => 500,
'variant_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]
]),
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/agent/me/shopper-subscriptions/{id}/update"
payload := strings.NewReader("{\n \"interval_count\": 26,\n \"items\": [\n {\n \"product_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"quantity\": 500,\n \"variant_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\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.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"interval_count\": 26,\n \"items\": [\n {\n \"product_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"quantity\": 500,\n \"variant_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update")
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 \"interval_count\": 26,\n \"items\": [\n {\n \"product_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"quantity\": 500,\n \"variant_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"subscription": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "active",
"offer_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"interval_unit": "week",
"interval_count": 1,
"renewal_mode": "off_session",
"items": [
{
"product_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"quantity": 2,
"variant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "Colombian Roast 1 kg"
}
],
"offer_label": "Subscribe & save 15%",
"next_delivery_on": "2026-11-28",
"fulfillment_method": "delivery",
"shipping_address_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"pickup_location_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"payment_method": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"brand": "visa",
"last4": "4242",
"exp_month": 123,
"exp_year": 123
},
"terms": {},
"deliveries_completed": 3,
"paused_until": "2023-12-25",
"cancel_effective_on": "2023-12-25",
"created_at": "2023-11-07T05:31:56Z",
"pending_action": {
"cycle_no": 123,
"reason": "<string>",
"confirm_url": "<string>"
}
}
},
"evidence": [
{}
],
"computed_at": "2023-11-07T05:31:56Z",
"environment": "production",
"currency": "<string>"
}{
"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": "not_found",
"message": "Resource not found"
}
}{
"error": {
"code": "conflict",
"message": "A customer with this email already exists for this store"
}
}{
"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.
Headers
Customer session token from POST /v1/auth/login or POST /v1/auth/verify-otp. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Bring-your-own-auth assertion identifying the customer. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
A raw token from the store's own identity provider, verified by the store's configured Auth verifier. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Path Parameters
The subscription.
Body
curl --request POST \
--url https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"interval_count": 26,
"items": [
{
"product_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"quantity": 500,
"variant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]
}
'import requests
url = "https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update"
payload = {
"interval_count": 26,
"items": [
{
"product_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"quantity": 500,
"variant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a"
}
]
}
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({
interval_count: 26,
items: [
{
product_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a',
quantity: 500,
variant_id: '3c90c3cc-0d44-4b50-8888-8dd25736052a'
}
]
})
};
fetch('https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update', 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/me/shopper-subscriptions/{id}/update",
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([
'interval_count' => 26,
'items' => [
[
'product_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a',
'quantity' => 500,
'variant_id' => '3c90c3cc-0d44-4b50-8888-8dd25736052a'
]
]
]),
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/agent/me/shopper-subscriptions/{id}/update"
payload := strings.NewReader("{\n \"interval_count\": 26,\n \"items\": [\n {\n \"product_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"quantity\": 500,\n \"variant_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\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.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"interval_count\": 26,\n \"items\": [\n {\n \"product_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"quantity\": 500,\n \"variant_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/agent/me/shopper-subscriptions/{id}/update")
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 \"interval_count\": 26,\n \"items\": [\n {\n \"product_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\",\n \"quantity\": 500,\n \"variant_id\": \"3c90c3cc-0d44-4b50-8888-8dd25736052a\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"data": {
"subscription": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"status": "active",
"offer_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"interval_unit": "week",
"interval_count": 1,
"renewal_mode": "off_session",
"items": [
{
"product_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"quantity": 2,
"variant_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"name": "Colombian Roast 1 kg"
}
],
"offer_label": "Subscribe & save 15%",
"next_delivery_on": "2026-11-28",
"fulfillment_method": "delivery",
"shipping_address_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"pickup_location_id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"payment_method": {
"id": "3c90c3cc-0d44-4b50-8888-8dd25736052a",
"brand": "visa",
"last4": "4242",
"exp_month": 123,
"exp_year": 123
},
"terms": {},
"deliveries_completed": 3,
"paused_until": "2023-12-25",
"cancel_effective_on": "2023-12-25",
"created_at": "2023-11-07T05:31:56Z",
"pending_action": {
"cycle_no": 123,
"reason": "<string>",
"confirm_url": "<string>"
}
}
},
"evidence": [
{}
],
"computed_at": "2023-11-07T05:31:56Z",
"environment": "production",
"currency": "<string>"
}{
"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": "not_found",
"message": "Resource not found"
}
}{
"error": {
"code": "conflict",
"message": "A customer with this email already exists for this store"
}
}{
"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"
}
}
