SKUHelm

REST API

The SKUHelm API

Plain JSON over HTTPS. Base address https://www.skuhelm.com/api/v1. There's also an OpenAPI document.

Keys

Make a key in the console under Settings, API and agents. A key belongs to one store and is shown once. Send it as Authorization: Bearer cs_live_.... Revoke it from the same page and it stops working at once.

POST/api/v1/catalog/validate

Validate items

Check up to 1000 feed rows against OpenAI's spec. With a key, your store settings fill gaps first, exactly as your live feed would.

Auth
Key optional
Limit
Without a key: 30 a minute per IP address
itemsbodyobject[]Feed rows. A bare array as the whole body works too.
apply_store_settingsbodyboolean?With a key, fill gaps from your store settings first. Default true.
request
curl -X POST https://www.skuhelm.com/api/v1/catalog/validate \
  -H "content-type: application/json" \
  -d '{"items":[{
    "item_id": "MUG-01",
    "title": "Speckled stoneware mug",
    "price": "24.00 GBP",
    "availability": "in_stock"
  }]}'
response
{
  "ok": true,
  "spec_version": "openai-feed-2026-10",
  "summary": { "total": 1, "valid": 0, "blocked": 1, "checkoutReady": 0, "score": 0, "rules": [...] },
  "items": [{
    "item_id": "MUG-01",
    "status": "blocked",
    "issues": [{ "field": "brand", "code": "missing_brand", "severity": "error", "message": "Brand is missing." }]
  }]
}
POST/api/v1/catalog/sync

Start a sync

Re-read every shop connected by address, record what changed and republish the feed. Uploaded files are skipped: upload a new file to update them.

Auth
API key
Plan
Pro
Limit
6 every 10 minutes per store
request
curl -X POST https://www.skuhelm.com/api/v1/catalog/sync \
  -H "authorization: Bearer $SKUHELM_KEY"
response
{
  "ok": true,
  "synced": [{ "source_id": "src_...", "ok": true, "added": 2, "updated": 14, "removed": 0, "feed_version": 18 }]
}
GET/api/v1/sync/status

Read sync status

Item counts by status, the live feed version and its addresses, each source and the last five syncs.

Auth
API key
request
curl https://www.skuhelm.com/api/v1/sync/status \
  -H "authorization: Bearer $SKUHELM_KEY"
response
{
  "ok": true,
  "store": { "slug": "your-shop", "name": "Your Shop", "plan": "pro" },
  "items": { "total": 412, "ready": 380, "no_checkout": 20, "blocked": 12, "left_out": 0, "gone_from_shop": 3 },
  "feed": { "version": 18, "published_at": "...", "item_count": 400, "checkout_ready": 380, "held_back_by_plan": 0, "urls": { ... } },
  "sources": [...],
  "recent_syncs": [...],
  "sync_schedule": "every 60 minutes"
}
GET/api/v1/feeds/{store}/acp.json

Fetch a published feed

The live feed as one JSON document with its version and counts. Public, like the feed files themselves.

Auth
Public
storepathstringThe store's slug, shown in the console under Feed.
request
curl https://www.skuhelm.com/api/v1/feeds/your-shop/acp.json
response
{
  "ok": true,
  "store": { "slug": "your-shop", "name": "Your Shop" },
  "version": 18,
  "item_count": 400,
  "files": { "json": "...", "jsonl": "...", "csv": "..." },
  "items": [{ "item_id": "MUG-01", "title": "Speckled stoneware mug", ... }]
}

Feed files

The feed itself, as OpenAI reads it. Public, cached for a minute, with an ETag so an unchanged feed answers 304.

  • GET /feeds/{store}.jsonl
  • GET /feeds/{store}.csv

Errors

Every error has the same shape, so code can branch on error.code without reading the message.

json
{ "ok": false, "error": { "code": "unauthorized", "message": "...", "hint": "...", "docs": "/docs/api#errors" } }
400bad_requestThe body or a parameter is wrong. The message says which.
401unauthorizedNo API key, or the key was revoked.
402payment_requiredThat needs the Pro plan.
404not_foundNo such store, or it hasn't published a feed yet.
422source_failedWe couldn't read the shop. The message says what it returned.
429rate_limitedToo many calls. Wait the number of seconds in Retry-After.
500internalOur fault. It has been reported.
503unavailableThe database is briefly unreachable. Try again shortly.