Getting Started
Everything you need to authenticate, make your first API call, and start building with the SpireStock REST API.
Prerequisites
- An active SpireStock workspace with admin or developer access.
- An API key, created from the dashboard under Developer → API Keys. Creating one requires workspace administrator access.
- A tool for making HTTP requests — cURL, Postman, or any HTTP client library.
Don't have a workspace yet?
Sign up at app.spirestock.com/signup to create a free workspace and start building immediately.
Base URL
All API endpoints are served from a single base URL. Every path referenced in this documentation is relative to:
https://api.spirestock.com/api/v1For example, the full URL for listing orders would be https://api.spirestock.com/api/v1/orders.
Authentication
SpireStock accepts two credentials. Which one you want depends on whether a browser is involved.
| Credential | Use it for | Lifetime |
|---|---|---|
API key — X-API-Key | Servers, scheduled jobs, ERP connectors, anything without a browser | Until you revoke it (or its optional expiry) |
JWT — Authorization: Bearer | Browser sessions signed in through the dashboard | 15 days, and only one active per user |
Server-side? Use an API key.
/auth/login requires a Cloudflare Turnstile token, which only a browser can obtain — a server calling it gets 400 Security verification required. On top of that, a user holds only one valid JWT at a time, so signing into the dashboard invalidates the token your integration was using. API keys have neither problem.
Authenticating with an API key
Create a key in the dashboard under Developer → API Keys, or with the API itself while signed in as an administrator:
curl -X POST https://api.spirestock.com/api/v1/developer/api-keys \
-H "Authorization: Bearer YOUR_SESSION_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Warehouse sync",
"scopes": "read,orders",
"expires_in_days": 90
}'{
"response_code": 201,
"message": "API key created. Save this key — it won't be shown again.",
"data": {
"id": 3,
"key": "df_9f3a1c7e2b8d4a6f0c5e1b7a3d9f2e4c6a8b0d2f4e6a8c0b2d4f6a8c0e2b4d6f",
"name": "Warehouse sync",
"key_prefix": "df_9f3a1c",
"scopes": "read,orders",
"expires_at": "2026-11-16T00:00:00.000Z"
}
}The key is shown exactly once
Only a SHA-256 hash of the key is stored, so it cannot be retrieved later — not by you, not by support. Save it to your secret store the moment you create it. Lost a key? Revoke it and issue a new one.
Send it on every request as a header:
X-API-Key: df_9f3a1c7e2b8d...Scopes
A key’s scopes string mixes two kinds of token: actions (read, write) and optional resources (orders, users, products, …). read permits GET and HEAD; write permits every method and implies read. Name no resource and the key reaches every endpoint it is allowed to; name one or more and it is confined to those.
| Scopes | What the key can do |
|---|---|
read | Read any endpoint |
read,orders | Read /orders and nothing else |
write,orders | Full access to /orders, read and write |
read,write | Full access to every allowed endpoint |
* | The same as read,write |
Endpoints an API key cannot reach
Regardless of scope, keys are refused on /auth, /signup, /developer, /billing, /platform, /super-admin, /portal, /driver-app and /support. That is why key and webhook management needs a signed-in administrator — a leaked key cannot mint more keys, repoint your webhooks, or start a purchase.
Authenticating with a session token
If you are building something a person signs into, exchange their credentials for a JWT. The request must carry a turnstile_token from the Turnstile widget on your page:
curl -X POST https://api.spirestock.com/api/v1/auth/login \
-H "Content-Type: application/json" \
-H "User-Agent: AcmeIntegration/1.0" \
-d '{
"email": "YOUR_EMAIL",
"password": "YOUR_PASSWORD",
"turnstile_token": "TOKEN_FROM_THE_TURNSTILE_WIDGET"
}'{
"response_code": 200,
"message": "Login successful",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"first_name": "Jane",
"last_name": "Developer",
"official_email": "[email protected]",
"role_name": "Admin",
"user_type": 0,
"organization_id": 5,
"organization_name": "Acme Dairy"
},
"workspace": {
"workspace_id": "ws_x9y8z7",
"slug": "acme-dairy",
"display_name": "Acme Dairy",
"plan": "professional",
"status": 1
}
}Include the token in the Authorization header using the Bearer scheme:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Token expiry & single active session
Tokens expire after 15 days. Only one is valid per user at a time — logging in again, logging out, or changing the password invalidates the previous one. When you receive 401 Unauthorized, re-authenticate.
Token storage best practices
Never store JWT tokens in localStorage or sessionStorage — they are accessible to any JavaScript running on the page (XSS-vulnerable). Instead:
- Server-side apps: Store tokens in memory or encrypted server-side sessions.
- Browser apps: Use
httpOnlycookies set by your backend proxy, or keep tokens in memory only (they will be lost on page refresh, which is the safer trade-off). - Never include tokens in URLs, log files, or error reporting payloads.
Set a User-Agent
The API rejects requests whose User-Agent is missing, shorter than 10 characters, or matches a known scraping tool. Several HTTP clients trip this by default:
| Client | Sends by default | Result |
|---|---|---|
| cURL | curl/8.5.0 | 403 |
Python requests | python-requests/2.31.0 | 403 |
Node fetch | node (4 characters) | 403 |
Two ways to satisfy this
Send X-API-Key — keyed requests skip the check entirely, because the key already identifies you. Or set a descriptive User-Agent naming your integration, which is good practice either way:
curl https://api.spirestock.com/api/v1/orders \
-H "X-API-Key: YOUR_API_KEY" \
-H "User-Agent: AcmeIntegration/1.0"const res = await fetch("https://api.spirestock.com/api/v1/orders", {
headers: {
"X-API-Key": process.env.SPIRESTOCK_API_KEY,
"User-Agent": "AcmeIntegration/1.0",
},
});import requests
session = requests.Session()
session.headers.update({
"X-API-Key": os.environ["SPIRESTOCK_API_KEY"],
"User-Agent": "AcmeIntegration/1.0",
})Your First Request
With a key in hand, fetch a list of orders from your workspace. Keep the key in an environment variable — never in source control.
curl https://api.spirestock.com/api/v1/orders \
-H "X-API-Key: $SPIRESTOCK_API_KEY"const res = await fetch("https://api.spirestock.com/api/v1/orders", {
headers: {
"X-API-Key": process.env.SPIRESTOCK_API_KEY,
},
});
const { data, pagination } = await res.json();
console.log(data); // Array of order objects
console.log(pagination.total);import os
import requests
res = requests.get(
"https://api.spirestock.com/api/v1/orders",
headers={"X-API-Key": os.environ["SPIRESTOCK_API_KEY"]},
)
result = res.json()
print(result["data"]) # List of order objects
print(result["pagination"]["total"])Every request is scoped to the organization the key belongs to. There is no tenant parameter to pass and no way to reach another workspace’s data.
What You Can Build
A single API key reaches most of the workspace — orders, outlets, products, routes, reports and more. A read key pulls data out; a write key pushes data back in. Below are the common integrations, and the endpoint groups each one uses.
| Build this | What it does | Endpoint groups |
|---|---|---|
| ERP / accounting sync | Keep SAP, Zoho, Oracle or Tally in step with SpireStock — masters and orders flowing both ways. | /masters, /orders, /products, /imports, /exports |
| Order automation | Create orders from an external system, or read order and delivery status into yours. | /orders, /secondary-sales |
| Master-data management | Bulk-manage outlets, SKUs, price lists, routes and the other 40+ master entities from your own tooling. | /masters, /imports |
| Reporting & BI pipelines | Feed sales, stock and activity data into a warehouse, dashboard or scheduled export. | /reports, /dashboard, /exports |
| Logistics & tracking | Sync vehicle, crate and live-location data with a fleet or WMS system. | /logistics, /tracking, /crates |
| Event-driven automation | React the moment something happens — an order is placed or delivered — instead of polling. | Webhooks |
For the full, always-current list of every endpoint and its schema, see the API Reference. A key can reach every group except the ones that manage credentials, billing or the platform itself — see Scopes above.
Building with AI instead of code
If the goal is to ask questions of the workspace rather than build a pipeline, connect an AI assistant over MCP instead of writing against the REST API directly. It wraps a read key and lets Claude, Cursor or any MCP-compatible client answer questions in plain language:
- “How many orders were delivered last week, by outlet?”
- “Which distributors haven’t ordered this month?”
- “Summarise today’s sales for the north region.”
The MCP connection is read-only — it can look at orders, accounts, products and reports, but never place an order, change a record or alter billing. See Connect AI Tools for setup.
Which one do I want?
API key — a builder’s credential. Automate, sync and integrate; read to pull, write to push.
MCP — an analyst’s credential. Let an AI read the workspace and answer questions. Read-only.
Rate Limits
To ensure fair usage across all tenants, the API enforces rate limits on a per-IP or per-token basis. When a limit is exceeded the API responds with 429 Too Many Requests.
| Endpoint Group | Limit | Window |
|---|---|---|
POST /signup/register | 5 requests | Per hour |
POST /auth/login | 20 requests | Per 15 minutes |
| General endpoints | 120 requests | Per minute |
| General endpoints | 2,000 requests | Per hour |
| Export endpoints | 5 requests | Per minute |
| Report endpoints | 20 requests | Per minute |
Rate-limit headers
Every response includes X-RateLimit-Limit and X-RateLimit-Remaining so you can throttle before you are throttled. A 429 also carries Retry-After in seconds.
Each key gets its own budget
An API key is metered separately from the administrator who created it, and separately from every other key. A busy integration cannot rate-limit a colleague out of the dashboard, and splitting work across two keys gives you two budgets.
Error Handling
The API uses standard HTTP status codes. Errors include a JSON body with a response_code matching the HTTP status and a human-readable message field.
{
"response_code": 400,
"message": "Validation failed"
}| Status Code | Meaning | Common Cause |
|---|---|---|
400 | Bad Request | Invalid or missing request body fields |
401 | Unauthorized | Missing, revoked, or expired credential |
403 | Forbidden | Missing scope, an endpoint API keys cannot reach, a suspended workspace, or a rejected User-Agent |
404 | Not Found | Resource does not exist or belongs to another workspace |
429 | Too Many Requests | Rate limit exceeded — back off and retry |
500 | Internal Server Error | Unexpected server-side failure — contact support if it persists |
Next Steps
Now that you can authenticate and make requests, explore further:
- API Reference — Full endpoint documentation with request/response schemas.
- Webhooks — Receive real-time event notifications for orders, users, and payments.
- SDKs — Official client libraries for Node.js, Python, and cURL examples.
- Connect AI Tools — Let Claude, Cursor, or any MCP client read your workspace in plain language. Published on npm today.
