Inventory sync that stops when the data looks wrong
It writes the fix. And it knows when not to.
Your warehouse says 40 in stock. Your shop says 12. Failclosed notices, writes 40 into the shop, and tells you it did. When the numbers look wrong for the wrong reason, it writes nothing and tells you which rule stopped it.
A normal day
shopSKU-1041 → 12
The day it goes wrong
Your warehouse API has a bad minute and returns an empty list. Nothing is broken in your business — the response is just wrong.
shop5,000 products, all now “missing”
source_empty — nothing was written, you were told, and it cost nothing.An ordinary sync tool cannot tell that empty list apart from a warehouse that sold everything, so it zeroes your shop. That has happened to real merchants across thousands of products at once. It is why most reconciliation tools stop at an email nobody reads — and it is the entire reason this one exists.
Everything it refuses to do
Eleven rules. Each has a code, so a refusal is something your systems can react to, not a note in a log. Five of them cannot be switched off — not by you, not for any price.
Refusals that stop the whole run
| Code | What it protects you from | Can be switched off |
|---|---|---|
source_empty | Every record on the other side looks missing. Far more often a bad minute at an API than a real decision to delete everything you sell. | always on |
source_population_collapsed | A partial response, a filter that silently applied, or a paginated read that stopped early. The records that vanished are not gone; they were simply not returned. | optional |
source_stale | Acting on a stale snapshot means correcting the present to match the past, which can be worse than leaving the disagreement alone. | optional |
duplicate_identifiers | Records cannot be matched reliably, so every conclusion drawn from the match is unsound. | always on |
too_few_records | Small samples make ordinary variation look like systemic disagreement. | optional |
divergence_too_wide | Wide disagreement usually means a source changed shape or failed, not that the world moved. Repairing on that basis multiplies the damage. | optional |
too_many_repairs | A ceiling on how much can change in one pass, whatever the reason. | optional |
no_repairs_configured | Nothing is repaired because nothing was configured to be repaired. | always on |
Refusals that stop one write
| Code | What it protects you from | Can be switched off |
|---|---|---|
destructive_not_permitted | Deleting is the one repair that cannot be undone by repeating it differently. It requires saying so on the check, not merely on the action. | always on |
target_changed_since_read | Somebody or something else has touched the record since we compared it. Writing now would overwrite a change we never saw. | optional |
non_idempotent_retry | A timeout is ambiguous: the write may well have landed. Repeating a non-idempotent repair could apply it twice. | always on |
Setting it up
- Tell it which system to believe. Your ERP, your warehouse, whatever holds the real number. Any endpoint that returns JSON.
- Point it at Shopify. Four fields: your shop, an access token, a location, and where the quantity sits in your data.
- Watch it for a few days. It starts in watch-only mode and repairs nothing. You read what it would have done. When you trust it, send the same call with
"repair": true.
# 1. The system you believe
curl -X POST https://YOUR-HOST/api/v1/sources \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "key": "warehouse",
"url": "https://wms.example/api/stock",
"secret_headers": { "Authorization": "Bearer ..." },
"records_pointer": "/items", "key_pointer": "/sku" }'
# 2. Shopify, the repair and the schedule, in one call
curl -X POST https://YOUR-HOST/api/v1/connectors/shopify \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "key": "stock-sync",
"shop": "acme-supplies",
"access_token": "shpat_...",
"location_id": "12345678",
"authoritative_source": "warehouse",
"quantity_pointer": "/quantity" }'
# => { "mode": "watch_only", ... }Being told
Nothing here expects you to sit watching a screen. Give it one URL and it calls you when a refusal saved you, when a source is unhealthy, when a check paused itself, and when a repair failed.
curl -X POST https://YOUR-HOST/api/v1/notifications \
-H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
-d '{ "url": "https://ops.example/hooks/failclosed" }'Signed with HMAC-SHA256 so you can verify the call came from us. Deduplicated per subject, so a source that has been broken since Tuesday is one message, not one every time the check runs.
When you do want to read a week yourself, the report page renders it from the key you already hold: how often the two systems disagreed, on which records, and whether anything was written.
Price
A 5,000-product catalogue checked daily, repairing 300 lines a month, costs about €16.50: €1.50 of watching and €15.00 of repairs. Prepaid from €25.00. No subscription, no seat fee.
What it cannot do
- It only knows what your endpoints say. If both systems agree on something wrong, it reports agreement.
- It compares snapshots. Two systems read seconds apart can disagree because something changed in between. A very short interval produces more of this.
- The repair is your request, sent by us. We do not know whether it is the right fix, only that you configured it and the rules allowed it.
- Only public https endpoints are reachable, by design.
API
| Endpoint | Purpose |
|---|---|
GET /api/v1/rules | Every refusal rule. No account. |
POST /api/v1/tenants | Create an account, receive an API key |
POST /api/v1/connectors/shopify | Shopify inventory in one call |
POST /api/v1/sources | Register any JSON source |
POST /api/v1/actions | Register a repair |
POST /api/v1/checks | Pair two sources, set the limits |
GET /api/v1/divergences | Open disagreements and their refusals |
GET /api/v1/runs | Run history, with what was refused and why |
GET /api/v1/corrections | Every repair sent, with what was in it |
POST /api/v1/notifications | Where to be told about refusals |
GET /api/v1/balance | Balance, rates, ledger |
POST /api/v1/credits/checkout | Buy credit |