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

  1. 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).
  2. Call from your backend only. The answer text is in content; provider and modelId say which model answered.
  3. 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

  1. Your backend asks Claspine for an upload link (call 1) and gives uploadUrl to the browser.
  2. 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.
  3. 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

  1. 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

  1. Claspine Atlas: connect your Stripe or Razorpay account on the Payments page first. Engine: configure the provider settings.
  2. Amounts are in minor units (4900 = 49.00). Send the person to checkoutUrl and keep orderId.
  3. 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

  1. Without amount, everything not refunded yet is returned. With amount (minor units), only that part.
  2. 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

  1. Configure the price first (Claspine Atlas: Payments page; engine: provider settings). priceKey is your name for it, for example pro-monthly.
  2. subject is what the subscription is for (a user, a team, a workspace); customerId is who pays. Use your own stable ids.
  3. 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

  1. 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.
  2. channel is email, sms (engine) or whatsapp; to is an email address or a phone number in international form. variables fill the template placeholders.
  3. 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.
  4. 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

  1. Claspine Atlas does not sign users in: keep your own login there (guide keep-your-own-login).
  2. Your login page signs the person in with Firebase and sends the ID token to your backend; your backend exchanges it (call 1).
  3. 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());