SP SPACEPLACE
Developer documentation

One API.
Token-gated.
Idempotent.

Every core object in Space Place has a stable HTTP API. Push Events from your POS, post signed Inventory Movements from a WMS, Receive Purchase Orders from a dock scanner. Authentication is a DataStream ID plus a Token — gated by scoped, time-bounded Provisioning entries.

Quick start

Three nouns. One request.

Before your first API call, you need three things — all created in the Connect section of the app.

  1. 1
    A DataStream — the logical channel events will land in. Every Organization starts with an "All Events" stream; create more for each system you'll push from.
  2. 2
    A Token — any string you want. Tokens authenticate the call.
  3. 3
    A Token Provision — gates that Token for a list of endpoints (or *) inside a start/end time window. Without an active Provision, the Token is inert.

Once those exist, copy the DataStream's UUID and the Token value and paste them into the request below.

POST /api/events
curl -X POST https://www.spaceplace.app/api/events \
  -H "Content-Type: application/json" \
  -H "X-DataStream-ID: cb71-4a…" \
  -H "X-Token: tk_live_…" \
  -d '{
    "number": 42,
    "source": "pos-04",
    "key": "sale",
    "value": "BEV-SPARK-16"
  }'
← 201 Created event recorded
Authentication

Every call carries a stream and a token.

There are two ways to send credentials. Headers are the recommended path; body fields exist for clients that can't easily set custom headers.

HTTP Headers recommended

X-DataStream-ID: your-datastream-uuid
X-Token: your-token-value

Request body

{
  "data_stream_id": "your-datastream-uuid",
  "token": "your-token-value"
}
How validation works. Each request checks (1) the DataStream exists and belongs to a non-suspended Organization, (2) a Token with that value exists in that Organization, and (3) at least one of that Token's Provisioning entries is currently active and permits the endpoint being called. Failure on any check returns 401 or 403.
Endpoint reference

All endpoints, one table.

Method Endpoint Description
GET /api/events List and search events with filtering, sorting, pagination.
GET /api/events/since Get events since a given datetime — ideal for poll loops.
GET /api/events/{id} Retrieve a single event by ID.
POST /api/events Create a new event. All event fields are optional.
PUT /api/events/{id} Update an existing event.
DELETE /api/events/{id} Soft-delete an event.
GET /api/inventory List on-hand inventory rows. Filter by location, product, UPC, or low / out.
POST /api/inventory/movements Post a signed-quantity movement to the ledger. Requires an idempotency_key.
POST /api/purchase-orders/{po}/receive Receive lines against a PO in a single batch. Idempotent on batch_key.
Events API

Five fields, all optional.

Every Event has a tiny, fixed shape. Defaults fill in anything you don't send, so the smallest valid POST is an empty body.

Event fields

Field Type Default
timestampstringnow (server)
numberinteger0
sourcestring""
keystring""
valuestring""

timestamp accepts ISO 8601 or Y-m-d H:i:s. The server also records remote_ip automatically.

List & search params

Parameter Description
sourceExact match on source
keyExact match on key
valuePartial match (case-insensitive)
number_min / number_maxNumeric range
from / toDatetime range
searchAcross source, key, value
sort / dirtimestamp · number · source · key · value · created_at — asc / desc
per_page / page1–200 (default 50)
Inventory API

Post movements to the ledger.

On-hand is a cache. The truth lives in the inventory_movements append-only ledger. Every write requires an idempotency_key — replaying the same key in the same Organization returns the prior movement without re-applying.

POST /api/inventory/movements
{
  "sku": "BEV-SPARK-16",
  "location_slug": "warehouse-01",
  "movement_type": "adjustment",
  "quantity": -3,
  "idempotency_key": "count-2026-05-25-row-7",
  "notes": "Spillage during cycle count"
}

Resolve the product by product_id, sku, or upc — in that precedence; the location by location_id or location_slug. GET /api/inventory accepts location_id, product_id, sku, upc, and a filter=low|out shortcut, and returns both identifiers on every product.

Addressing a product. sku is the reliable identifier: every product has one, and it reaches goods that were never listed externally (raw components, sub-assemblies) and therefore have no barcode. upc is the optional marketplace barcode and remains fully supported, so integrations written before SKUs existed keep working unchanged.

Purchase orders API

Receive a PO in one call.

Match lines by line_id, product_id, sku, or upc. The batch_key (e.g. the vendor's packing-slip number) makes the call idempotent — replay it and you get the same Vendor Receipt back, not a second one.

Tracked products: lot-tracked lines require lot_number (optional expiration_date, vendor_lot_no); serial-tracked lines require a serials array with exactly qty entries.

POST /api/purchase-orders/{po}/receive
{
  "batch_key": "slip-2026-05-25-A",
  "notes": "Two cartons dented; flagged for QC",
  "lines": [
    { "sku": "BEV-SPARK-16", "qty": 144 },
    { "sku": "SNK-GRANOLA-6", "qty": 36 }
  ]
}
Examples

Working samples in PHP and Bash.

Fifteen end-to-end recipes — single posts, batches, headers vs. body auth, error handling, reusable clients, URL monitors, long-running daemons, listing, polling, updating, deleting. Each example exists as both a PHP and Bash script. Click any tile to read it with syntax highlighting and one-click copy.

Or jump to the full examples index.

HTTP responses

What every status code means.

200 OK

Successful read, update, or delete.

201 Created

A new event, movement, or receipt was recorded.

401 Unauthorized

Missing or unknown DataStream ID or Token value.

403 Forbidden

Token has no currently active Provision for this endpoint.

404 Not Found

Event, product, location, or PO not in this Organization.

422 Unprocessable

Validation failed — body returns the offending fields.

Ready to integrate

Start pushing
in five minutes.

Create a DataStream, mint a Token, provision it. Then any system that can speak HTTP can write into Space Place.