---
title: "Getting Started with the SpireStock API"
description: "Authenticate, make your first API call, handle errors, and understand rate limits."
source: https://0.0.0.0:8006/docs/getting-started
---

# 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](https://app.spirestock.com/signup?utm_source=spirestock&utm_medium=developer_portal&utm_campaign=developer_onboarding&utm_content=getting_started_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:

```bash
https://api.spirestock.com/api/v1
```

For 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:

Create a key 

```bash
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 (201 Created) 

```json
{
  "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:

```bash
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`, `/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 or repoint your webhooks.

### 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:

Request 

```bash
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 (200 OK) 

```json
{
  "response_code": 200,
  "message": "Login successful",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "user": {
    "id": 1,
    "first_name": "Jane",
    "last_name": "Developer",
    "official_email": "developer@yourcompany.com",
    "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:

```bash
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 `httpOnly` cookies 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:

Identifying your client 

**cURL**

```bash
curl https://api.spirestock.com/api/v1/orders \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "User-Agent: AcmeIntegration/1.0"
```

**Node.js**

```javascript
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",
  },
});
```

**Python**

```python
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.

GET /orders 

**cURL**

```bash
curl https://api.spirestock.com/api/v1/orders \
  -H "X-API-Key: $SPIRESTOCK_API_KEY"
```

**Node.js**

```javascript
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);
```

**Python**

```python
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.

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

Error response example 

```json
{
  "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**](/api-reference) — Full endpoint documentation with request/response schemas.
-   [**Webhooks**](/docs/webhooks) — Receive real-time event notifications for orders, users, and payments.
-   [**SDKs**](/docs/sdks) — Official client libraries for Node.js, Python, and cURL examples.
