Update customer
Partially update the authenticated customer’s profile. Only the fields provided in the request body are updated.
Customer-self authentication required. Provide exactly one of:
x-auth-token— Galactic Core customer session JWT.x-external-auth— bring-your-own-auth assertion (HMAC-signed claim carryingexternal_idand a(iat, exp)window ≤ 300 seconds).x-idp-token— a raw token from the store’s own identity provider, which Galactic Core forwards to the store’s Auth verifier (fail-closed).
Mismatch returns 403. Providing more than one of these headers returns
400.
Only the profile fields listed in the request body are writable. Any other
field on the customer record (for example tier, total_spent,
total_purchases or external_id) is ignored. A body naming no writable
field leaves the customer unchanged and returns it.
email is writable only when the request is made with a secret key, from
the storefront’s server after it has verified the new address. With a
publishable key, email is ignored like any other non-writable field,
because guest orders are linked to a customer by email address. Changing
email to an address another customer of the store already uses returns
409.
curl --request PATCH \
--url https://api.tybritelabs.com/v1/customers/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"email": "jsmith@example.com",
"phone": "<string>",
"name": "<string>",
"address": "<string>",
"marketing_consent": true
}
'import requests
url = "https://api.tybritelabs.com/v1/customers/{id}"
payload = {
"email": "jsmith@example.com",
"phone": "<string>",
"name": "<string>",
"address": "<string>",
"marketing_consent": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
email: 'jsmith@example.com',
phone: '<string>',
name: '<string>',
address: '<string>',
marketing_consent: true
})
};
fetch('https://api.tybritelabs.com/v1/customers/{id}', 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/customers/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'email' => 'jsmith@example.com',
'phone' => '<string>',
'name' => '<string>',
'address' => '<string>',
'marketing_consent' => true
]),
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/customers/{id}"
payload := strings.NewReader("{\n \"email\": \"jsmith@example.com\",\n \"phone\": \"<string>\",\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"marketing_consent\": true\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.tybritelabs.com/v1/customers/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"jsmith@example.com\",\n \"phone\": \"<string>\",\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"marketing_consent\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/customers/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"email\": \"jsmith@example.com\",\n \"phone\": \"<string>\",\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"marketing_consent\": true\n}"
response = http.request(request)
puts response.read_body{
"customer": {
"id": "de11e44b-78e5-4940-ad54-cac237d5d30d",
"name": "John Doe",
"email": "john.doe@example.com",
"phone": "+12125550199",
"address": "350 Fifth Avenue, New York, NY 10118",
"status": "active",
"join_date": "2026-06-20",
"external_id": null,
"marketing_consent": true,
"total_purchases": 0,
"store_credit_balance": 0,
"created_at": "2026-06-20T13:47:38.831799+00:00",
"updated_at": "2026-06-20T13:47:38.831799+00:00"
}
}{
"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
Galactic Core customer session access_token. The resolved customer must match the {id} path parameter. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Bring-your-own-auth assertion. Format: <base64url(JSON)>.<base64url(HMAC-SHA256(JSON))> where the JSON is { "external_id": "...", "iat": <unix>, "exp": <unix> } signed with the store's hmac_secret. Claim lifetime capped at 300 seconds. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
A raw token from the store's own identity provider (e.g. a Firebase ID token). Galactic Core forwards it to the store's configured Auth verifier, which validates it and returns the identity; the resolved customer must match the {id} path parameter.
Verification is fail-closed: if the verifier rejects the token or is unreachable, the request is unauthenticated (401). Requires an Auth verifier to be configured for the store. Provide exactly one of x-auth-token, x-external-auth, or x-idp-token.
Path Parameters
Body
Response
Customer updated successfully
Show child attributes
Show child attributes
curl --request PATCH \
--url https://api.tybritelabs.com/v1/customers/{id} \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"email": "jsmith@example.com",
"phone": "<string>",
"name": "<string>",
"address": "<string>",
"marketing_consent": true
}
'import requests
url = "https://api.tybritelabs.com/v1/customers/{id}"
payload = {
"email": "jsmith@example.com",
"phone": "<string>",
"name": "<string>",
"address": "<string>",
"marketing_consent": True
}
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.patch(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PATCH',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
email: 'jsmith@example.com',
phone: '<string>',
name: '<string>',
address: '<string>',
marketing_consent: true
})
};
fetch('https://api.tybritelabs.com/v1/customers/{id}', 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/customers/{id}",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PATCH",
CURLOPT_POSTFIELDS => json_encode([
'email' => 'jsmith@example.com',
'phone' => '<string>',
'name' => '<string>',
'address' => '<string>',
'marketing_consent' => true
]),
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/customers/{id}"
payload := strings.NewReader("{\n \"email\": \"jsmith@example.com\",\n \"phone\": \"<string>\",\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"marketing_consent\": true\n}")
req, _ := http.NewRequest("PATCH", 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.patch("https://api.tybritelabs.com/v1/customers/{id}")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"email\": \"jsmith@example.com\",\n \"phone\": \"<string>\",\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"marketing_consent\": true\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/customers/{id}")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Patch.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"email\": \"jsmith@example.com\",\n \"phone\": \"<string>\",\n \"name\": \"<string>\",\n \"address\": \"<string>\",\n \"marketing_consent\": true\n}"
response = http.request(request)
puts response.read_body{
"customer": {
"id": "de11e44b-78e5-4940-ad54-cac237d5d30d",
"name": "John Doe",
"email": "john.doe@example.com",
"phone": "+12125550199",
"address": "350 Fifth Avenue, New York, NY 10118",
"status": "active",
"join_date": "2026-06-20",
"external_id": null,
"marketing_consent": true,
"total_purchases": 0,
"store_credit_balance": 0,
"created_at": "2026-06-20T13:47:38.831799+00:00",
"updated_at": "2026-06-20T13:47:38.831799+00:00"
}
}{
"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"
}
}
