Exchange authorization code for API keys
Exchanges the one-time authorization code (received in the callback redirect) for the merchant’s API key pair. Call this from your server — never from a browser.
Authentication: client_id and client_secret in the request body.
Your client_secret is issued when your application is approved. Store
it securely server-side and never expose it client-side.
The code is single-use and expires after 10 minutes. Attempting to exchange an already-used or expired code returns an error.
Store sk and signing_secret — both are returned only once. The
sk authenticates every request you make for that merchant. The
signing_secret signs the writes that require a signature (creating and
updating orders, initializing payments): set X-Timestamp to the current
Unix time and X-Signature to the base64 HMAC-SHA256 of
{timestamp}.{requestBody}. Neither value can be retrieved later.
Rate limit: 60 requests/minute per IP address.
Security: Always verify that the state value in the callback
matches what you sent before calling this endpoint.
Attempt limits: 30 exchanges per client_id from one calling address, and 5 per authorization
code, in any 10 minutes; beyond that the call returns 429 rate_limited with a Retry-After header.
Past 200 exchanges for one client_id across all addresses, it is slowed to one every 2 seconds.
curl --request POST \
--url https://api.tybritelabs.com/v1/connect/token \
--header 'Content-Type: application/json' \
--data '
{
"code": "abc123def456ghi789jkl012mno345pq",
"client_id": "fantastic-storefront",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourapp.com/callback"
}
'import requests
url = "https://api.tybritelabs.com/v1/connect/token"
payload = {
"code": "abc123def456ghi789jkl012mno345pq",
"client_id": "fantastic-storefront",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourapp.com/callback"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
code: 'abc123def456ghi789jkl012mno345pq',
client_id: 'fantastic-storefront',
client_secret: 'YOUR_CLIENT_SECRET',
redirect_uri: 'https://yourapp.com/callback'
})
};
fetch('https://api.tybritelabs.com/v1/connect/token', 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/connect/token",
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([
'code' => 'abc123def456ghi789jkl012mno345pq',
'client_id' => 'fantastic-storefront',
'client_secret' => 'YOUR_CLIENT_SECRET',
'redirect_uri' => 'https://yourapp.com/callback'
]),
CURLOPT_HTTPHEADER => [
"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/connect/token"
payload := strings.NewReader("{\n \"code\": \"abc123def456ghi789jkl012mno345pq\",\n \"client_id\": \"fantastic-storefront\",\n \"client_secret\": \"YOUR_CLIENT_SECRET\",\n \"redirect_uri\": \"https://yourapp.com/callback\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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/connect/token")
.header("Content-Type", "application/json")
.body("{\n \"code\": \"abc123def456ghi789jkl012mno345pq\",\n \"client_id\": \"fantastic-storefront\",\n \"client_secret\": \"YOUR_CLIENT_SECRET\",\n \"redirect_uri\": \"https://yourapp.com/callback\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/connect/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"code\": \"abc123def456ghi789jkl012mno345pq\",\n \"client_id\": \"fantastic-storefront\",\n \"client_secret\": \"YOUR_CLIENT_SECRET\",\n \"redirect_uri\": \"https://yourapp.com/callback\"\n}"
response = http.request(request)
puts response.read_body{
"sk": "tybrite_sk_live_...",
"pk": "tybrite_pk_live_...",
"signing_secret": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"pair_id": "550e8400-e29b-41d4-a716-446655440000",
"store_id": "660f9511-f30c-52e5-b827-557766551111",
"environment": "production",
"scopes": [
"read",
"orders:read"
],
"client_id": "fantastic-storefront"
}{
"error": {
"code": "invalid_request",
"message": "Missing required field",
"details": "The 'email' field is required",
"hint": "<string>",
"available_stock": 123,
"current_cart_quantity": 123,
"limit": 123,
"remaining": 123,
"reset": 123
}
}{
"error": {
"code": "invalid_client",
"message": "Invalid client credentials"
}
}{
"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"
}
}Body
The one-time authorization code from the callback ?code= parameter.
"abc123def456ghi789jkl012mno345pq"
Your application's client identifier.
"fantastic-storefront"
Your application's client secret. Never send this from a browser.
"YOUR_CLIENT_SECRET"
Must exactly match the redirect URI used in the original authorization request.
"https://yourapp.com/callback"
Response
Code exchanged successfully — store the sk securely server-side
Secret key. Store this securely server-side — it will not be shown again. Use it for all server-side API calls on behalf of the merchant.
"tybrite_sk_live_..."
Publishable key. Safe to use client-side (browser/mobile). Use it for catalogue reads and the actions a shopper takes for themselves, such as cart and wishlist.
"tybrite_pk_live_..."
Unique identifier for this key pair. Store it — required to call POST /v1/connect/revoke.
"550e8400-e29b-41d4-a716-446655440000"
The UUID of the store the merchant authorized access to.
"660f9511-f30c-52e5-b827-557766551111"
Environment the key pair is scoped to.
sandbox, production "production"
The permission scopes granted by the merchant.
["read", "orders:read"]
Your application's client identifier.
"fantastic-storefront"
Request-signing secret for THIS connection. Store it securely server-side alongside the
secret key — it will not be shown again. Use it to sign write requests that require a
signature (creating/updating orders, initializing payments): set X-Timestamp to the
current Unix time and X-Signature to the HMAC-SHA256 of {timestamp}.{request_body}
using this secret. Scoped to this connection, so it can be rotated or revoked
independently of any other integration.
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
Present only if your application registered a webhook URL. When it did, connecting a merchant
automatically creates a signed webhook subscription for their store — you do not need to call
createWebhookEndpoint yourself. The subscription starts receiving events immediately.
Verify each inbound event POST with webhook_endpoint.signing_secret (this is a different
secret from signing_secret above: signing_secret signs YOUR outbound write requests, while
webhook_endpoint.signing_secret verifies Galactic Core's inbound event POSTs to you). The
secret is returned only once, here.
Show child attributes
Show child attributes
{
"id": "770a8400-e29b-41d4-a716-446655442222",
"url": "https://yourapp.com/webhooks/galactic-core",
"events": [
"promotion.activated",
"review.approved",
"feature.status_changed"
],
"signing_secret": "4f2a...c0de"
}
curl --request POST \
--url https://api.tybritelabs.com/v1/connect/token \
--header 'Content-Type: application/json' \
--data '
{
"code": "abc123def456ghi789jkl012mno345pq",
"client_id": "fantastic-storefront",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourapp.com/callback"
}
'import requests
url = "https://api.tybritelabs.com/v1/connect/token"
payload = {
"code": "abc123def456ghi789jkl012mno345pq",
"client_id": "fantastic-storefront",
"client_secret": "YOUR_CLIENT_SECRET",
"redirect_uri": "https://yourapp.com/callback"
}
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
code: 'abc123def456ghi789jkl012mno345pq',
client_id: 'fantastic-storefront',
client_secret: 'YOUR_CLIENT_SECRET',
redirect_uri: 'https://yourapp.com/callback'
})
};
fetch('https://api.tybritelabs.com/v1/connect/token', 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/connect/token",
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([
'code' => 'abc123def456ghi789jkl012mno345pq',
'client_id' => 'fantastic-storefront',
'client_secret' => 'YOUR_CLIENT_SECRET',
'redirect_uri' => 'https://yourapp.com/callback'
]),
CURLOPT_HTTPHEADER => [
"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/connect/token"
payload := strings.NewReader("{\n \"code\": \"abc123def456ghi789jkl012mno345pq\",\n \"client_id\": \"fantastic-storefront\",\n \"client_secret\": \"YOUR_CLIENT_SECRET\",\n \"redirect_uri\": \"https://yourapp.com/callback\"\n}")
req, _ := http.NewRequest("POST", url, payload)
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/connect/token")
.header("Content-Type", "application/json")
.body("{\n \"code\": \"abc123def456ghi789jkl012mno345pq\",\n \"client_id\": \"fantastic-storefront\",\n \"client_secret\": \"YOUR_CLIENT_SECRET\",\n \"redirect_uri\": \"https://yourapp.com/callback\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.tybritelabs.com/v1/connect/token")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Content-Type"] = 'application/json'
request.body = "{\n \"code\": \"abc123def456ghi789jkl012mno345pq\",\n \"client_id\": \"fantastic-storefront\",\n \"client_secret\": \"YOUR_CLIENT_SECRET\",\n \"redirect_uri\": \"https://yourapp.com/callback\"\n}"
response = http.request(request)
puts response.read_body{
"sk": "tybrite_sk_live_...",
"pk": "tybrite_pk_live_...",
"signing_secret": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"pair_id": "550e8400-e29b-41d4-a716-446655440000",
"store_id": "660f9511-f30c-52e5-b827-557766551111",
"environment": "production",
"scopes": [
"read",
"orders:read"
],
"client_id": "fantastic-storefront"
}{
"error": {
"code": "invalid_request",
"message": "Missing required field",
"details": "The 'email' field is required",
"hint": "<string>",
"available_stock": 123,
"current_cart_quantity": 123,
"limit": 123,
"remaining": 123,
"reset": 123
}
}{
"error": {
"code": "invalid_client",
"message": "Invalid client credentials"
}
}{
"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"
}
}
