SETUP GUIDES · RECIPES
Recipes
The exact calls for common tasks, for Claspine Atlas and the self-hosted engine. They are generated from one description and checked against Claspine’s API on every build. AI agents get the same recipes from the MCP tool get_recipe.
Call the AI gateway
Send text to the AI gateway; Claspine picks the model from your route and falls back to the next one if a provider fails.
Works on: Claspine Atlas, self-hosted engine
- Claspine Atlas: store a provider key in Secret Vault and set up an AI Gateway route first. Engine: give it a provider key (for example PLATFORM_LLM_GROQ_APIKEYREFERENCE).
- Call from your backend only. The answer text is in content; provider and modelId say which model answered.
- Optional: intent selects a named route; variables fill placeholders in an intent prompt.
Claspine Atlas
curl
# Complete
curl -s -X POST "https://api.claspine.com/api/v1/llm/complete" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input":"Summarise this support ticket in two sentences: the export fails with a timeout."}' Node.js
// Node.js 18+ (global fetch)
// Complete
const res = await fetch('https://api.claspine.com/api/v1/llm/complete', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"input":"Summarise this support ticket in two sentences: the export fails with a timeout."}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Complete
res = requests.post(
"https://api.claspine.com/api/v1/llm/complete",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"input":"Summarise this support ticket in two sentences: the export fails with a timeout."},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Complete
req, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/llm/complete", strings.NewReader(`{"input":"Summarise this support ticket in two sentences: the export fails with a timeout."}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Complete
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/llm/complete"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"input\":\"Summarise this support ticket in two sentences: the export fails with a timeout.\"}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# Complete
curl -s -X POST "$SPINE_API_URL/api/v1/llm/complete" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"input":"Summarise this support ticket in two sentences: the export fails with a timeout."}' Node.js
// Node.js 18+ (global fetch)
// Complete
const res = await fetch(`${process.env.SPINE_API_URL}/api/v1/llm/complete`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"input":"Summarise this support ticket in two sentences: the export fails with a timeout."}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Complete
res = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/llm/complete",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"input":"Summarise this support ticket in two sentences: the export fails with a timeout."},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Complete
req, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/llm/complete", strings.NewReader(`{"input":"Summarise this support ticket in two sentences: the export fails with a timeout."}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("X-Spine-Host", "app.example.com")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Complete
HttpRequest req = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/llm/complete"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"input\":\"Summarise this support ticket in two sentences: the export fails with a timeout.\"}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Upload a file straight to your bucket
Get a signed upload link, let the browser upload directly to your bucket, then register the file with Claspine.
Works on: Claspine Atlas, self-hosted engine
- Your backend asks Claspine for an upload link (call 1) and gives uploadUrl to the browser.
- The browser uploads the bytes with HTTP PUT to uploadUrl, with the same Content-Type. Azure Blob Storage also needs the header x-ms-blob-type: BlockBlob. Nothing goes through your backend or Claspine.
- Your backend registers the upload with the storageLocator from call 1 (call 2) and keeps the returned file id for downloads.
Claspine Atlas
curl
# 1. Ask for an upload link
curl -s -X POST "https://api.claspine.com/api/v1/files/presigned-upload" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"contentType":"image/png","sizeBytes":48213}'
# 2. Register the uploaded file
curl -s -X POST "https://api.claspine.com/api/v1/files/register" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213}' Node.js
// Node.js 18+ (global fetch)
// 1. Ask for an upload link
const res1 = await fetch('https://api.claspine.com/api/v1/files/presigned-upload', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"contentType":"image/png","sizeBytes":48213}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Register the uploaded file
const res2 = await fetch('https://api.claspine.com/api/v1/files/register', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213}),
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Ask for an upload link
res1 = requests.post(
"https://api.claspine.com/api/v1/files/presigned-upload",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"contentType":"image/png","sizeBytes":48213},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Register the uploaded file
res2 = requests.post(
"https://api.claspine.com/api/v1/files/register",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Ask for an upload link
req1, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/files/presigned-upload", strings.NewReader(`{"contentType":"image/png","sizeBytes":48213}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Register the uploaded file
req2, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/files/register", strings.NewReader(`{"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213}`))
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req2.Header.Set("Content-Type", "application/json")
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Ask for an upload link
HttpRequest req1 = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/files/presigned-upload"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"contentType\":\"image/png\",\"sizeBytes\":48213}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Register the uploaded file
HttpRequest req2 = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/files/register"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"storageLocator\":\"<storageLocator from the first call>\",\"contentType\":\"image/png\",\"sizeBytes\":48213}"))
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# 1. Ask for an upload link
curl -s -X POST "$SPINE_API_URL/api/v1/files/presigned-upload" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"contentType":"image/png","sizeBytes":48213}'
# 2. Register the uploaded file
curl -s -X POST "$SPINE_API_URL/api/v1/files/register" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213}' Node.js
// Node.js 18+ (global fetch)
// 1. Ask for an upload link
const res1 = await fetch(`${process.env.SPINE_API_URL}/api/v1/files/presigned-upload`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"contentType":"image/png","sizeBytes":48213}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Register the uploaded file
const res2 = await fetch(`${process.env.SPINE_API_URL}/api/v1/files/register`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213}),
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Ask for an upload link
res1 = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/files/presigned-upload",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"contentType":"image/png","sizeBytes":48213},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Register the uploaded file
res2 = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/files/register",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Ask for an upload link
req1, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/files/presigned-upload", strings.NewReader(`{"contentType":"image/png","sizeBytes":48213}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("X-Spine-Host", "app.example.com")
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Register the uploaded file
req2, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/files/register", strings.NewReader(`{"storageLocator":"<storageLocator from the first call>","contentType":"image/png","sizeBytes":48213}`))
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req2.Header.Set("X-Spine-Host", "app.example.com")
req2.Header.Set("Content-Type", "application/json")
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Ask for an upload link
HttpRequest req1 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/files/presigned-upload"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"contentType\":\"image/png\",\"sizeBytes\":48213}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Register the uploaded file
HttpRequest req2 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/files/register"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"storageLocator\":\"<storageLocator from the first call>\",\"contentType\":\"image/png\",\"sizeBytes\":48213}"))
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString()); Give someone a download link
A signed link to one stored file, valid for a few minutes.
Works on: Claspine Atlas, self-hosted engine
- Check in your backend that the person may see the file, then ask Claspine for the link and hand it to them.
Claspine Atlas
curl
# Ask for a download link
curl -s -X POST "https://api.claspine.com/api/v1/files/presigned-download" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"fileReferenceId":"<file id from register>","expiryMinutes":10}' Node.js
// Node.js 18+ (global fetch)
// Ask for a download link
const res = await fetch('https://api.claspine.com/api/v1/files/presigned-download', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"fileReferenceId":"<file id from register>","expiryMinutes":10}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Ask for a download link
res = requests.post(
"https://api.claspine.com/api/v1/files/presigned-download",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"fileReferenceId":"<file id from register>","expiryMinutes":10},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Ask for a download link
req, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/files/presigned-download", strings.NewReader(`{"fileReferenceId":"<file id from register>","expiryMinutes":10}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Ask for a download link
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/files/presigned-download"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"fileReferenceId\":\"<file id from register>\",\"expiryMinutes\":10}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# Ask for a download link
curl -s -X POST "$SPINE_API_URL/api/v1/files/presigned-download" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"fileReferenceId":"<file id from register>","expiryMinutes":10}' Node.js
// Node.js 18+ (global fetch)
// Ask for a download link
const res = await fetch(`${process.env.SPINE_API_URL}/api/v1/files/presigned-download`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"fileReferenceId":"<file id from register>","expiryMinutes":10}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Ask for a download link
res = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/files/presigned-download",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"fileReferenceId":"<file id from register>","expiryMinutes":10},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Ask for a download link
req, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/files/presigned-download", strings.NewReader(`{"fileReferenceId":"<file id from register>","expiryMinutes":10}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("X-Spine-Host", "app.example.com")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Ask for a download link
HttpRequest req = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/files/presigned-download"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"fileReferenceId\":\"<file id from register>\",\"expiryMinutes\":10}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Take a one-time payment
Open a hosted checkout page for an amount, then read the order to see whether it was paid.
Works on: Claspine Atlas, self-hosted engine
- Claspine Atlas: connect your Stripe or Razorpay account on the Payments page first. Engine: configure the provider settings.
- Amounts are in minor units (4900 = 49.00). Send the person to checkoutUrl and keep orderId.
- Do not trust the return to successUrl alone: read the order (call 2) or receive the payment callback. CAPTURED means paid.
Claspine Atlas
curl
# 1. Create the checkout
curl -s -X POST "https://api.claspine.com/api/v1/payments/checkout/one-time" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"}'
# 2. Read the order (ORDER_ID: the orderId from the checkout)
curl -s -X GET "https://api.claspine.com/api/v1/payments/orders/ORDER_ID" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" Node.js
// Node.js 18+ (global fetch)
// 1. Create the checkout
const res1 = await fetch('https://api.claspine.com/api/v1/payments/checkout/one-time', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Read the order (ORDER_ID: the orderId from the checkout)
const res2 = await fetch('https://api.claspine.com/api/v1/payments/orders/ORDER_ID', {
method: 'GET',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
},
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Create the checkout
res1 = requests.post(
"https://api.claspine.com/api/v1/payments/checkout/one-time",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Read the order (ORDER_ID: the orderId from the checkout)
res2 = requests.get(
"https://api.claspine.com/api/v1/payments/orders/ORDER_ID",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Create the checkout
req1, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/payments/checkout/one-time", strings.NewReader(`{"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Read the order (ORDER_ID: the orderId from the checkout)
req2, _ := http.NewRequest("GET", "https://api.claspine.com/api/v1/payments/orders/ORDER_ID", nil)
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Create the checkout
HttpRequest req1 = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/payments/checkout/one-time"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"amount\":4900,\"currency\":\"usd\",\"successUrl\":\"https://app.example.com/paid\",\"cancelUrl\":\"https://app.example.com/cart\"}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Read the order (ORDER_ID: the orderId from the checkout)
HttpRequest req2 = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/payments/orders/ORDER_ID"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# 1. Create the checkout
curl -s -X POST "$SPINE_API_URL/api/v1/payments/checkout/one-time" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"}'
# 2. Read the order (ORDER_ID: the orderId from the checkout)
curl -s -X GET "$SPINE_API_URL/api/v1/payments/orders/ORDER_ID" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" Node.js
// Node.js 18+ (global fetch)
// 1. Create the checkout
const res1 = await fetch(`${process.env.SPINE_API_URL}/api/v1/payments/checkout/one-time`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Read the order (ORDER_ID: the orderId from the checkout)
const res2 = await fetch(`${process.env.SPINE_API_URL}/api/v1/payments/orders/ORDER_ID`, {
method: 'GET',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
},
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Create the checkout
res1 = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/payments/checkout/one-time",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Read the order (ORDER_ID: the orderId from the checkout)
res2 = requests.get(
os.environ["SPINE_API_URL"] + "/api/v1/payments/orders/ORDER_ID",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Create the checkout
req1, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/payments/checkout/one-time", strings.NewReader(`{"amount":4900,"currency":"usd","successUrl":"https://app.example.com/paid","cancelUrl":"https://app.example.com/cart"}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("X-Spine-Host", "app.example.com")
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Read the order (ORDER_ID: the orderId from the checkout)
req2, _ := http.NewRequest("GET", os.Getenv("SPINE_API_URL") + "/api/v1/payments/orders/ORDER_ID", nil)
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req2.Header.Set("X-Spine-Host", "app.example.com")
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Create the checkout
HttpRequest req1 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/payments/checkout/one-time"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"amount\":4900,\"currency\":\"usd\",\"successUrl\":\"https://app.example.com/paid\",\"cancelUrl\":\"https://app.example.com/cart\"}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Read the order (ORDER_ID: the orderId from the checkout)
HttpRequest req2 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/payments/orders/ORDER_ID"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString()); Refund a payment, fully or in part
Refund what is left of a paid order, or a given amount. A refund the provider refuses fails and the order stays paid.
Works on: Claspine Atlas, self-hosted engine
- Without amount, everything not refunded yet is returned. With amount (minor units), only that part.
- If the call answers PAYMENT_REFUND_UNAVAILABLE, retry later with the same amount; PAYMENT_REFUND_REJECTED means the provider refused.
Claspine Atlas
curl
# Refund 10.00 (ORDER_ID: the paid order)
curl -s -X POST "https://api.claspine.com/api/v1/payments/orders/ORDER_ID/refund" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"amount":1000}' Node.js
// Node.js 18+ (global fetch)
// Refund 10.00 (ORDER_ID: the paid order)
const res = await fetch('https://api.claspine.com/api/v1/payments/orders/ORDER_ID/refund', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"amount":1000}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Refund 10.00 (ORDER_ID: the paid order)
res = requests.post(
"https://api.claspine.com/api/v1/payments/orders/ORDER_ID/refund",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"amount":1000},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Refund 10.00 (ORDER_ID: the paid order)
req, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/payments/orders/ORDER_ID/refund", strings.NewReader(`{"amount":1000}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Refund 10.00 (ORDER_ID: the paid order)
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/payments/orders/ORDER_ID/refund"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"amount\":1000}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# Refund 10.00 (ORDER_ID: the paid order)
curl -s -X POST "$SPINE_API_URL/api/v1/payments/orders/ORDER_ID/refund" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"amount":1000}' Node.js
// Node.js 18+ (global fetch)
// Refund 10.00 (ORDER_ID: the paid order)
const res = await fetch(`${process.env.SPINE_API_URL}/api/v1/payments/orders/ORDER_ID/refund`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"amount":1000}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Refund 10.00 (ORDER_ID: the paid order)
res = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/payments/orders/ORDER_ID/refund",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"amount":1000},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Refund 10.00 (ORDER_ID: the paid order)
req, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/payments/orders/ORDER_ID/refund", strings.NewReader(`{"amount":1000}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("X-Spine-Host", "app.example.com")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Refund 10.00 (ORDER_ID: the paid order)
HttpRequest req = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/payments/orders/ORDER_ID/refund"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"amount\":1000}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Start a subscription
Open a hosted subscription checkout for a price you configured, for one of your customers.
Works on: Claspine Atlas, self-hosted engine
- Configure the price first (Claspine Atlas: Payments page; engine: provider settings). priceKey is your name for it, for example pro-monthly.
- subject is what the subscription is for (a user, a team, a workspace); customerId is who pays. Use your own stable ids.
- Read the state later with GET /payments/subscriptions/{subject}.
Claspine Atlas
curl
# Create the subscription checkout
curl -s -X POST "https://api.claspine.com/api/v1/payments/checkout/subscription" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"}' Node.js
// Node.js 18+ (global fetch)
// Create the subscription checkout
const res = await fetch('https://api.claspine.com/api/v1/payments/checkout/subscription', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Create the subscription checkout
res = requests.post(
"https://api.claspine.com/api/v1/payments/checkout/subscription",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Create the subscription checkout
req, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/payments/checkout/subscription", strings.NewReader(`{"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Create the subscription checkout
HttpRequest req = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/payments/checkout/subscription"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"subject\":\"team-42\",\"customerId\":\"user-123\",\"priceKey\":\"pro-monthly\",\"successUrl\":\"https://app.example.com/billing?done=1\",\"cancelUrl\":\"https://app.example.com/billing\"}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# Create the subscription checkout
curl -s -X POST "$SPINE_API_URL/api/v1/payments/checkout/subscription" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"}' Node.js
// Node.js 18+ (global fetch)
// Create the subscription checkout
const res = await fetch(`${process.env.SPINE_API_URL}/api/v1/payments/checkout/subscription`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"}),
});
if (!res.ok) throw new Error((await res.json()).code); // errors: { code, detail, type: link to the fix }
const data = await res.json(); Python
import os, requests
# Create the subscription checkout
res = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/payments/checkout/subscription",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"},
timeout=30,
)
res.raise_for_status() # errors: res.json()["code"]; ["type"] links to the fix
data = res.json() Go
// import ("net/http"; "os"; "strings")
// Create the subscription checkout
req, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/payments/checkout/subscription", strings.NewReader(`{"subject":"team-42","customerId":"user-123","priceKey":"pro-monthly","successUrl":"https://app.example.com/billing?done=1","cancelUrl":"https://app.example.com/billing"}`))
req.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req.Header.Set("X-Spine-Host", "app.example.com")
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// Create the subscription checkout
HttpRequest req = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/payments/checkout/subscription"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"subject\":\"team-42\",\"customerId\":\"user-123\",\"priceKey\":\"pro-monthly\",\"successUrl\":\"https://app.example.com/billing?done=1\",\"cancelUrl\":\"https://app.example.com/billing\"}"))
.build();
HttpResponse<String> res = HttpClient.newHttpClient().send(req, HttpResponse.BodyHandlers.ofString()); Send an email or WhatsApp message
Send a template to an email address or phone number your backend knows, through your own provider account.
Works on: Claspine Atlas, self-hosted engine · full guide
- Claspine Atlas: set up the channel under Messaging (your Amazon SES, SMTP or AWS End User Messaging account), add the template, and use an API key with the messages:send scope. sp_test_ keys reach only confirmed test recipients. Engine: configure the provider (platform.ses.*, platform.smtp.* or platform.whatsapp.*) and give the service key the messages scope.
- channel is email, sms (engine) or whatsapp; to is an email address or a phone number in international form. variables fill the template placeholders.
- Send an Idempotency-Key header with a value of your own per message (for example order-A-42-shipped): a retry with the same key returns the first message instead of sending twice.
- status is ACCEPTED when the provider took the message; RETRYABLE_FAILURE is retried for you; TERMINAL_FAILURE carries the provider answer in failureDetail. Read it again later by id (call 2).
Claspine Atlas
curl
# 1. Send
curl -s -X POST "https://api.claspine.com/api/v1/messages" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}'
# 2. Read it back
curl -s -X GET "https://api.claspine.com/api/v1/messages/MESSAGE_ID" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" Node.js
// Node.js 18+ (global fetch)
// 1. Send
const res1 = await fetch('https://api.claspine.com/api/v1/messages', {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Read it back
const res2 = await fetch('https://api.claspine.com/api/v1/messages/MESSAGE_ID', {
method: 'GET',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
},
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Send
res1 = requests.post(
"https://api.claspine.com/api/v1/messages",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"Content-Type": "application/json",
},
json={"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Read it back
res2 = requests.get(
"https://api.claspine.com/api/v1/messages/MESSAGE_ID",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Send
req1, _ := http.NewRequest("POST", "https://api.claspine.com/api/v1/messages", strings.NewReader(`{"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Read it back
req2, _ := http.NewRequest("GET", "https://api.claspine.com/api/v1/messages/MESSAGE_ID", nil)
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Send
HttpRequest req1 = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/messages"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"channel\":\"email\",\"to\":\"customer@example.com\",\"template\":\"order.shipped\",\"variables\":{\"name\":\"Ravi\",\"order\":\"A-42\"}}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Read it back
HttpRequest req2 = HttpRequest.newBuilder(URI.create("https://api.claspine.com/api/v1/messages/MESSAGE_ID"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString()); Self-hosted engine
curl
# 1. Send
curl -s -X POST "$SPINE_API_URL/api/v1/messages" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}'
# 2. Read it back
curl -s -X GET "$SPINE_API_URL/api/v1/messages/MESSAGE_ID" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" Node.js
// Node.js 18+ (global fetch)
// 1. Send
const res1 = await fetch(`${process.env.SPINE_API_URL}/api/v1/messages`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Read it back
const res2 = await fetch(`${process.env.SPINE_API_URL}/api/v1/messages/MESSAGE_ID`, {
method: 'GET',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
},
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Send
res1 = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/messages",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Read it back
res2 = requests.get(
os.environ["SPINE_API_URL"] + "/api/v1/messages/MESSAGE_ID",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Send
req1, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/messages", strings.NewReader(`{"channel":"email","to":"customer@example.com","template":"order.shipped","variables":{"name":"Ravi","order":"A-42"}}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("X-Spine-Host", "app.example.com")
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Read it back
req2, _ := http.NewRequest("GET", os.Getenv("SPINE_API_URL") + "/api/v1/messages/MESSAGE_ID", nil)
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req2.Header.Set("X-Spine-Host", "app.example.com")
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Send
HttpRequest req1 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/messages"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"channel\":\"email\",\"to\":\"customer@example.com\",\"template\":\"order.shipped\",\"variables\":{\"name\":\"Ravi\",\"order\":\"A-42\"}}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Read it back
HttpRequest req2 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/messages/MESSAGE_ID"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString()); Sign a user in and check the session (engine)
Exchange a Firebase ID token for a Claspine session, then check on each request who is signed in.
Works on: self-hosted engine · full guide
- Claspine Atlas does not sign users in: keep your own login there (guide keep-your-own-login).
- Your login page signs the person in with Firebase and sends the ID token to your backend; your backend exchanges it (call 1).
- Keep sessionToken in an httpOnly cookie. On each request, check it (call 2) or use the Node SDK middleware; 401 means signed out.
Self-hosted engine
curl
# 1. Exchange the credential
curl -s -X POST "$SPINE_API_URL/api/v1/auth/sessions" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Content-Type: application/json" \
-d '{"provider":"firebase-auth","credential":"<Firebase ID token>","transport":"BEARER"}'
# 2. Who is signed in
curl -s -X GET "$SPINE_API_URL/api/v1/auth/me" \
-H "X-Spine-Api-Key: $SPINE_API_KEY" \
-H "X-Spine-Host: app.example.com" \
-H "Authorization: Bearer $SESSION_TOKEN" Node.js
// Node.js 18+ (global fetch)
// 1. Exchange the credential
const res1 = await fetch(`${process.env.SPINE_API_URL}/api/v1/auth/sessions`, {
method: 'POST',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Content-Type': 'application/json',
},
body: JSON.stringify({"provider":"firebase-auth","credential":"<Firebase ID token>","transport":"BEARER"}),
});
if (!res1.ok) throw new Error((await res1.json()).code); // errors: { code, detail, type: link to the fix }
const data1 = await res1.json();
// 2. Who is signed in
const res2 = await fetch(`${process.env.SPINE_API_URL}/api/v1/auth/me`, {
method: 'GET',
headers: {
'X-Spine-Api-Key': process.env.SPINE_API_KEY,
'X-Spine-Host': 'app.example.com',
'Authorization': 'Bearer ' + process.env.SESSION_TOKEN,
},
});
if (!res2.ok) throw new Error((await res2.json()).code); // errors: { code, detail, type: link to the fix }
const data2 = await res2.json(); Python
import os, requests
# 1. Exchange the credential
res1 = requests.post(
os.environ["SPINE_API_URL"] + "/api/v1/auth/sessions",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Content-Type": "application/json",
},
json={"provider":"firebase-auth","credential":"<Firebase ID token>","transport":"BEARER"},
timeout=30,
)
res1.raise_for_status() # errors: res1.json()["code"]; ["type"] links to the fix
data1 = res1.json()
# 2. Who is signed in
res2 = requests.get(
os.environ["SPINE_API_URL"] + "/api/v1/auth/me",
headers={
"X-Spine-Api-Key": os.environ["SPINE_API_KEY"],
"X-Spine-Host": "app.example.com",
"Authorization": "Bearer " + os.environ["SESSION_TOKEN"],
},
timeout=30,
)
res2.raise_for_status() # errors: res2.json()["code"]; ["type"] links to the fix
data2 = res2.json() Go
// import ("net/http"; "os"; "strings")
// 1. Exchange the credential
req1, _ := http.NewRequest("POST", os.Getenv("SPINE_API_URL") + "/api/v1/auth/sessions", strings.NewReader(`{"provider":"firebase-auth","credential":"<Firebase ID token>","transport":"BEARER"}`))
req1.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req1.Header.Set("X-Spine-Host", "app.example.com")
req1.Header.Set("Content-Type", "application/json")
res1, err := http.DefaultClient.Do(req1) // non-2xx: JSON with code; type links to the fix
// 2. Who is signed in
req2, _ := http.NewRequest("GET", os.Getenv("SPINE_API_URL") + "/api/v1/auth/me", nil)
req2.Header.Set("X-Spine-Api-Key", os.Getenv("SPINE_API_KEY"))
req2.Header.Set("X-Spine-Host", "app.example.com")
req2.Header.Set("Authorization", "Bearer " + os.Getenv("SESSION_TOKEN"))
res2, err := http.DefaultClient.Do(req2) // non-2xx: JSON with code; type links to the fix Java
// import java.net.URI; import java.net.http.*;
// 1. Exchange the credential
HttpRequest req1 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/auth/sessions"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Content-Type", "application/json")
.method("POST", HttpRequest.BodyPublishers.ofString("{\"provider\":\"firebase-auth\",\"credential\":\"<Firebase ID token>\",\"transport\":\"BEARER\"}"))
.build();
HttpResponse<String> res1 = HttpClient.newHttpClient().send(req1, HttpResponse.BodyHandlers.ofString());
// 2. Who is signed in
HttpRequest req2 = HttpRequest.newBuilder(URI.create(System.getenv("SPINE_API_URL") + "/api/v1/auth/me"))
.header("X-Spine-Api-Key", System.getenv("SPINE_API_KEY"))
.header("X-Spine-Host", "app.example.com")
.header("Authorization", "Bearer " + System.getenv("SESSION_TOKEN"))
.method("GET", HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> res2 = HttpClient.newHttpClient().send(req2, HttpResponse.BodyHandlers.ofString());