developers

Built to be inspected.

If you're evaluating build-vs-buy, this is the part that usually decides it. Here is exactly how the API, the two-way Shopify sync and the webhook layer actually work — not a pitch deck.

REST, versioned

A JSON REST API on api.retailcommerceos.com under /api/v1 (Hono on the Bun runtime). No GraphQL gymnastics — predictable, workspace-scoped resource URLs. Live in-app updates run over a separate internal WebSocket channel that isn't part of the key-authenticated surface.

Two auth modes

Browser sessions use HttpOnly cookies (Better Auth). Server-to-server integrations use scoped rk_live_ API keys passed as a Bearer token — issued per workspace, revocable, and never logged.

One data model

Customers, orders, inventory and products are one normalized Postgres schema shared by every module — not a dozen apps with a dozen copies. Read it once, trust it everywhere.

Workspace-scoped

Every request is scoped to the calling workspace at the query layer. An API key can never read or write another tenant's data, even with a guessed ID.

Scoped keys, revocable any time

Issue a per-workspace API key from settings, pass it as a Bearer token, revoke it the moment a contractor leaves. Keys are stored hashed; the raw value is shown once at creation and never appears in logs. The API is available on Pro plans and above.

# Server-to-server requests use a scoped workspace API key.
curl "https://api.retailcommerceos.com/api/v1/orders?pageSize=50" \
  -H "Authorization: Bearer rk_live_••••••••••••••••" \
  -H "Accept: application/json"

# 200 OK — page through with page & pageSize
{
  "items": [ { "id": "ord_8fk3...", "code": "#1042", "total": "4990.00", ... } ],
  "page": 1,
  "pageSize": 50,
  "total": 214,
  "hasMore": true
}

The hard parts, documented

Every vendor says 'two-way sync.' The edge cases live in conflict resolution, idempotency and retries — so here is how each one is handled.

Deterministic conflict resolution

When Shopify and Retail Commerce OS both write the same record, resolution is field-level and last-writer-wins by source timestamp, with inventory treated as authoritative on our side. No silent clobbering — the losing value is recorded in the sync audit log so you can see exactly what changed and revert it.

Idempotent writes

Every inbound webhook and push carries an idempotency key. Replays (Shopify retries aggressively during flash sales) are de-duplicated, so a doubled webhook never doubles an order or an inventory adjustment.

Dead-letter queue + retries

Outbound pushes that fail are retried with exponential backoff via BullMQ. After max attempts they land in a dead-letter queue surfaced in the in-app Sync Health panel — failures are visible and replayable, not lost.

Daily drift reconciliation

A scheduled job diffs Retail Commerce OS against Shopify every day and reconciles divergence (deletes, manual Shopify edits, missed webhooks). GID-matched so renamed or re-handled products still line up.

// Register an endpoint from Settings → Webhooks and every delivery is HMAC-signed.
// Verify the signature, then de-dupe on the delivery id before you act.
POST https://your-app.example.com/retailos-hook
Content-Type: application/json
X-RetailOS-Event: inventory.updated
X-RetailOS-Delivery: 8fk3a1f...                // stable across retries of a delivery
X-RetailOS-Signature: sha256=4a7d...           // HMAC-SHA256 of the raw body

{
  "event": "inventory.updated",
  "data": {
    "productId": "prod_9f2c...", "variantId": null, "locationId": "loc_surat_01",
    "onHand": 3, "previousOnHand": 5, "delta": -2
  },
  "timestamp": "2026-06-15T11:28:56.439Z"
}

// Failed deliveries retry up to 5 times with exponential backoff.
// X-RetailOS-Delivery stays the same across those retries — key your
// de-duplication on it. Header names are case-insensitive (RFC 9110).

Reliable when it matters most

Webhooks are HMAC-signed and idempotency-keyed. We retry with exponential backoff and dead-letter what we can't deliver — so a peak-sale traffic spike degrades gracefully instead of dropping events on the floor.

The full v1 surface

Every route below is workspace-scoped and requires a scoped rk_live_ key. The complete reference — request/response schemas for each field — is shared with technical evaluators on request.

MethodEndpoint
GET/api/v1/products
GET/api/v1/products/:id
POST/api/v1/products
PATCH/api/v1/products/:id
GET/api/v1/orders
GET/api/v1/orders/:id
PATCH/api/v1/orders/:id
GET/api/v1/customers
POST/api/v1/customers
GET/api/v1/inventory
PATCH/api/v1/inventory
GET/api/v1/me

Order creation stays in-app only — it runs pricing, loyalty redemption, cart rules and inventory reservation together, and we'd rather not expose a second path that can drift from it. Shopify sync controls and webhook endpoint registration are configured from the dashboard (Settings → Integrations / Webhooks), not through the API-key surface.

Rate limits

Every API key gets 600 requests/minute, bucketed on the key itself rather than a shared IP — one integration's traffic spike can't starve another. Limits are returned in X-RateLimit-* headers so you can back off cleanly.

Sandbox workspace

Spin up a free trial workspace, connect a Shopify dev store, and run a synthetic load test against real sync behaviour before you commit a single engineering hour. See migration.

Your data stays portable

Full export of products, orders, customers and inventory as JSON/CSV at any time — no lock-in. If you ever leave, you take a complete copy with you.

Want the full reference and an API key?

We'll share the complete endpoint reference, request/response schemas, sync-latency benchmarks and a sandbox so your team can validate it firsthand — before you remove anything you've already built.

Request API access

Inspect it before you trust it.

That's the point. Connect a dev store and put the sync under load — no card required.