Developer docs
The Spool List API
A REST API for Enterprise and Global subscribers: bulk-import machines and Forge listings into your own account from your systems. This page documents every endpoint that is live today. New endpoints are announced in the changelog.
Overview
| Base URL | https://api.thespoollist.com |
|---|---|
| Format | JSON request and response bodies (Content-Type: application/json) |
| Auth | API key in the Authorization header — see Authentication |
| Who can use it | Accounts on an Enterprise or Global subscription |
| Where to call it from | Your server. Browser requests from other websites are not allowed by the API's CORS policy, and an API key must never be shipped in client-side code. |
Authentication
Create a key in the Developer panel of The Spool List app (Enterprise and Global accounts), or with POST /v1/api/keys.
The full key is shown once, when it is created — only its 8-character prefix is stored in readable form, so copy it straight away.
Keys look like this:
spl_live_<8 hex characters>_<32 hex characters>
Send the key as a bearer token on every request:
curl https://api.thespoollist.com/v1/api/me \
-H "Authorization: Bearer spl_live_xxxxxxxx_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
- A missing or badly formed key returns
401{"error":"Missing or malformed API key"}; an unknown or revoked key returns401{"error":"Invalid API key"}. - The account that owns the key must be on an Enterprise or Global subscription, otherwise
402(see Errors). - After 20 failed authentication attempts from one IP address within 5 minutes, that address is locked out for 15 minutes (
429with aRetry-Afterheader). - Revoking a key in the app or with
DELETE /v1/api/keys/:idtakes effect on the next request.
Scopes
Each key carries one or more scopes, chosen when it is created. Scopes are written with a colon (listings:write); the underscore form (listings_write) is accepted too.
| Scope | Used by |
|---|---|
listings:write | POST /v1/api/machines/bulk, POST /v1/api/listings/bulk |
listings:read | Reserved — no released endpoint requires it yet |
jobs:read | Reserved — no released endpoint requires it yet |
earnings:read | Reserved — no released endpoint requires it yet |
webhooks | Reserved — webhooks are not available yet |
GET /v1/api/me needs no scope. A request without a required scope returns 403 {"error":"Missing required scope: listings_write"}.
Rate limits
Limits apply per account — all of an account's keys share them.
| Subscription | Per minute | Per day |
|---|---|---|
| Enterprise | 300 requests | 100,000 requests |
| Global | 1,000 requests | 500,000 requests |
Every authenticated response carries the current state:
X-RateLimit-Limit-Minute: 300
X-RateLimit-Remaining-Minute: 299
X-RateLimit-Limit-Day: 100000
X-RateLimit-Remaining-Day: 99999
Each window starts with the first request after the previous window ended. When a limit is reached the API answers 429 with a Retry-After header (seconds) and a body such as
{"error":"Rate limit exceeded (per-minute)","retry_after_sec":12}. Limits are hard caps; there are no overage charges.
Errors
Errors are JSON with an error string.
| Status | Meaning |
|---|---|
400 | The request body is invalid (for bulk endpoints: empty array, batch too large, or no valid rows — see each endpoint). |
401 | Missing, malformed, unknown or revoked key. |
402 | The account is not on an Enterprise or Global subscription. Body: {"error":"API access requires an Enterprise or Global subscription","current_tier":"…","upgrade_url":"…"} |
403 | The key lacks a scope the endpoint requires. |
429 | Rate limit reached, or too many failed authentication attempts. Wait for Retry-After seconds. |
500 | Server error. Safe to retry GET requests; for bulk imports, check what was created before retrying. |
Get the key's account
GET/v1/api/me
Returns the account that owns the key and the key's own prefix and scopes — a quick way to check a key works.
{
"id": "1f0c…-uuid",
"display_name": "Your shop name",
"subscription_tier": "enterprise",
"is_owner": true,
"has_completed_onboarding": true,
"key": { "prefix": "a1b2c3d4", "scopes": ["listings:write"] }
}
Bulk-register machines
POST/v1/api/machines/bulk
Scope listings:write. Adds up to 500 machines to the key's account in one request (body up to 1 MB). Same fields as the CSV import in the app.
Every new machine starts with verification status pending, and is always added to the key's own account.
| Field | Type | |
|---|---|---|
template_id | string | Required |
category | string | Required |
manufacturer | string | Required |
model | string | Required |
model_number | string | Optional |
nickname | string | Optional, up to 80 characters |
capabilities | string[] | Optional |
is_skill_station | boolean | Optional, default false |
curl -X POST https://api.thespoollist.com/v1/api/machines/bulk \
-H "Authorization: Bearer $SPOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"machines":[{"template_id":"…","category":"three_d_printing","manufacturer":"Prusa","model":"MK4","capabilities":["pla","petg"]}]}'
Response 201:
{ "inserted": 1, "skipped": 0, "errors": [] }
Rows that fail validation are skipped and listed in errors as {"index": n, "reason": "…"}; the valid rows are still created.
If no row is valid the response is 400 {"error":"no valid rows","errors":[…]}. More than 500 rows returns 400 with received set to the count sent.
Bulk-create Forge listings
POST/v1/api/listings/bulk
Scope listings:write. Creates up to 200 Forge listings on the key's account in one request (body up to 2 MB). Same fields as the CSV import in the app.
| Field | Type | |
|---|---|---|
title | string | Required, stored up to 120 characters |
description | string | Required, stored up to 2,000 characters |
price_cents | integer | Required, ≥ 0, in US cents |
currency | string | Required, must be "USD" — Forge listings are priced and charged in US dollars only |
category | string | Required, one of miniatures, home_decor, cosplay, jewelry, gadgets, art, gifts, replacement, fashion, electronics, automotive, rc_drones, education, outdoor, other |
material | string | Required, up to 80 characters |
photo_urls | string[] | Required: at least one https:// URL; up to 8 are kept and the first is the thumbnail |
color | string | Optional, up to 40 characters |
tags | string[] | Optional, up to 10 |
print_time_hours | number | Optional |
is_available | boolean | Optional, default true |
allows_custom_orders | boolean | Optional, default false |
curl -X POST https://api.thespoollist.com/v1/api/listings/bulk \
-H "Authorization: Bearer $SPOOL_API_KEY" \
-H "Content-Type: application/json" \
-d '{"listings":[{"title":"Articulated dragon","description":"Print-in-place, 30 cm.","price_cents":2400,"currency":"USD","category":"miniatures","material":"PLA","photo_urls":["https://example.com/dragon.jpg"]}]}'
Response 201 has the same shape as machines: {"inserted": n, "skipped": m, "errors": [{"index": i, "reason": "…"}]}.
The maker name and photo on each listing come from the account's profile.
Key management
These endpoints manage keys, so they are authenticated with the account's signed-in session token (the same bearer token the app uses), not with an API key — you cannot create your first key with a key.
They also require an Enterprise or Global subscription (402 otherwise).
List keys
GET/v1/api/keys
Up to 50 keys, newest first: {"keys":[{"id","label","key_prefix","scopes","is_revoked","last_used_at","created_at"}]}. Full keys are never returned.
Create a key
POST/v1/api/keys
{ "label": "Inventory sync", "scopes": ["listings:write"] }
label (up to 80 characters) and at least one valid scope are required. Response 201:
{
"key": "spl_live_a1b2c3d4_…",
"metadata": { "id": "…", "label": "Inventory sync", "key_prefix": "a1b2c3d4", "scopes": ["listings_write"], "created_at": "…" },
"note": "This is the only time the full key is shown. Copy it now — you won't be able to see it again."
}
An account can have up to 20 active keys; creating another returns 429 until one is revoked.
Revoke a key
DELETE/v1/api/keys/:id
Revokes one of the account's keys: {"revoked":{"id","key_prefix","is_revoked":true}}, or 404 if the key is not the account's. Revoking cannot be undone.
Health
GET/v1/health
Public, no key. Returns {"status":"ok", …, "timestamp":"…"} while the API is up — use it for uptime checks rather than an authenticated endpoint, so checks do not count against your rate limit.
Support
Questions about the API, or something here that does not match what you see: support@thespoollist.com.