Quickstart.
API v1Go from zero to a live integration in about ten minutes. You'll create an API key, place an order over the REST API, read the response, and see how to react to events — the same surface the dashboard uses, because every Sparx feature is an API endpoint first.
Overview
Sparx is API-first: the dashboard, the storefront, and AI agents over MCP are all consumers of the same REST and GraphQL surface. An integration touches three things — an authenticated client (your API key), a resource you read or write, and the events Sparx emits in response.
Build the integration
Four steps, each independently runnable against your tenant.
Create an API key
In your dashboard, open Settings → AI integrations and create a key. The secret (sk_live_…) is shown once — store it as SPARX_KEY. Full details in Authentication.
Place your first order
Orders live in the CRM, which owns the customer and order spine. POST a customer id and one or more line items; Sparx computes the totals. Here it is in three languages — no SDK required, just HTTP:
curl https://api.sparx.works/v1/crm/orders \
-H "Authorization: Bearer $SPARX_KEY" \
-H "Content-Type: application/json" \
-d '{
"customerId": "8a1f0b2c-9d3e-4a5b-8c6d-1e2f3a4b5c6d",
"currency": "USD",
"items": [
{ "sku": "INJ-6.7-CR", "name": "6.7L Common-Rail Injector", "quantity": 8, "unitPrice": 289.50 }
]
}'const res = await fetch("https://api.sparx.works/v1/crm/orders", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SPARX_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
customerId: "8a1f0b2c-9d3e-4a5b-8c6d-1e2f3a4b5c6d",
currency: "USD",
items: [
{ sku: "INJ-6.7-CR", name: "6.7L Common-Rail Injector", quantity: 8, unitPrice: 289.5 },
],
}),
});
const { data: order } = await res.json();import os, requests
res = requests.post(
"https://api.sparx.works/v1/crm/orders",
headers={"Authorization": f"Bearer {os.environ['SPARX_KEY']}"},
json={
"customerId": "8a1f0b2c-9d3e-4a5b-8c6d-1e2f3a4b5c6d",
"currency": "USD",
"items": [
{"sku": "INJ-6.7-CR", "name": "6.7L Common-Rail Injector", "quantity": 8, "unitPrice": 289.50}
],
},
)
order = res.json()["data"]Read the response
Every response shares one envelope: { success: true, data }. A created order comes back with a server-assigned id, an orderNumber, computed totals, and its lifecycle status:
{
"success": true,
"data": {
"id": "0c7b1a2d-4e5f-4a6b-9c8d-2e1f0a9b8c7d",
"orderNumber": "1042",
"status": "placed",
"paymentStatus": "unpaid",
"customerId": "8a1f0b2c-9d3e-4a5b-8c6d-1e2f3a4b5c6d",
"currency": "USD",
"subtotal": 2316.00,
"total": 2316.00,
"items": [
{ "sku": "INJ-6.7-CR", "name": "6.7L Common-Rail Injector", "quantity": 8, "unitPrice": 289.50 }
],
"createdAt": "2026-06-05T17:41:09Z"
}
}| Field | Type | Description |
|---|---|---|
id | string | The order’s unique id (UUID). |
orderNumber | string | Human-facing sequence number, auto-generated. |
status | enum | placed · fulfilled · delivered · cancelled · refunded |
paymentStatus | enum | unpaid · partially_paid · paid · refunded |
total | number | Computed from the line items, in currency units (e.g. 2316.00). |
createdAt | string | ISO-8601 timestamp, always UTC. |
React to events
Creating that order emitted an order.created event on the internal bus. To receive events in your own app, register a webhook and verify each signed delivery. Today the subscribable events are content, media, and redirect events — the full model is in Webhooks & events:
// Receive webhook deliveries — see the Webhooks guide for the full setup.
import { verifySparxWebhook } from "./verify";
export async function POST(req: Request) {
const raw = await req.text(); // the RAW body, for signing
const sig = req.headers.get("x-sparx-signature") ?? "";
if (!verifySparxWebhook(raw, sig, process.env.SPARX_WEBHOOK_SECRET)) {
return new Response("bad signature", { status: 400 });
}
const event = JSON.parse(raw); // { id, type, tenant_id, data, … }
if (event.type === "content.entry.published") {
await reindex(event.data); // your code
}
return new Response("ok"); // 2xx acknowledges delivery
}Conventions
A couple of patterns that apply across every endpoint — worth knowing before you go further.
The response envelope
Success is { "success": true, "data": … }. List endpoints add a meta object with pagination (total, per_page, next_cursor). Failures are { "success": false, "error": { "code", "message" } } with a machine-readable error.code.
Pagination
List endpoints accept take (page size, default 50) and skip (offset), and return meta.total and meta.next_cursor so you can page through large result sets.
Modules must be active
An endpoint whose module isn’t enabled for your tenant returns 403 module_disabled rather than partial behavior. Placing an order requires the crm module; see Core concepts.
Errors & status codes
Sparx uses conventional HTTP status codes and returns a machine-readable error.code on every failure. Handle these at minimum:
| Status | Code | When |
|---|---|---|
| 201 | ok | The order was created. |
| 401 | unauthorized | Missing, malformed, revoked, or expired API key. |
| 403 | module_disabled | The key’s tenant hasn’t activated that module. |
| 429 | rate_limited | Too many requests — back off and retry. |
“AI builds it, Sparx keeps it.” Every endpoint you call today is versioned and deprecation-warned — never silently broken under you.
— Sparx API design principle
Frequently asked
Do I need the dashboard to use the API?
No. The dashboard is one consumer of the API — you can run an entire tenant headless. The only thing that requires the dashboard is creating your first API key.
Is GraphQL or REST recommended?
Both are first-class and derived from one schema. Use REST for simple writes and webhooks; reach for GraphQL when you need to fetch a deep object graph in one round trip.
Next steps
sk_live_ format, scopes, and rotation.