Developers
The API is the platform, not a companion to it.
Our own till has no private endpoints. Everything it does, an integration can do through the same contract — published as OpenAPI 3.1, with generated clients and published standards in place of house conventions.
Spec first
The spec is the contract, and every client's types come from it.
One OpenAPI 3.1 document, split by resource and bundled into the single input that generates the Go server, the till's wire types and the back office screens. Edit the spec, regenerate, or the build stops. A field that is not in the contract does not compile.
This is not a preference. The till once read a tax field off a product that had never existed in any version of the contract, so every rate was zero and every sale reached the books with no tax on it. Generated types are how that stops being possible.
PUT /v1/orders/0192d4e8-7c1a-7b3e-9f12-4a6c8e0b5d21
Idempotency-Key: "sale-0187-attempt"
Content-Type: application/json
{
"location_id": "0192d4e0-11aa-7c00-8b1e-2f4d6a8c0e13",
"lines": [
{ "product_id": "…", "quantity": 2,
"unit_price": { "amount_minor": 450, "currency": "GBP" } }
],
"payments": [
{ "method": "cash",
"amount": { "amount_minor": 900, "currency": "GBP" },
"buyer_supplied": { "amount_minor": 1000, "currency": "GBP" } }
]
}The identifier is minted on the till, so a retry targets the same order. Money is integer minor units with a currency. The change due is worked out by the server from what the customer handed over — it is never accepted from the device.
What CI runs
Five gates between the contract and a till.
Each closes one way the code and the contract can drift apart. A failing gate stops a build rather than being noticed after somebody has run it.
- 01
Spec
The bundle is linted against a house ruleset: an idempotency key on every POST, a conditional header on every update, cursor pagination on every list, problem documents on every error, and no float anywhere near money. The ruleset is itself tested against a deliberately terrible spec, because two rules once shipped silently broken and only the fixture caught them.
- 02
Spec to code
Everything generated is regenerated and compared. Generated code cannot lag the contract, and a hand-edit to it fails rather than quietly serving last week's version.
- 03
Code to spec
The server is generated in strict mode, so an undeclared status code has no type to return. Returning one is a compile error rather than a surprise in production.
- 04
Layers
The dependency graph is declared and enforced. Domain types know nothing about infrastructure, the store owns all the SQL, and each ban carries a written reason.
- 05
Compatibility
Every change is diffed against the published contract. A till in the field runs a build we cannot force-upgrade, so a breaking change has to be an explicit decision.
Standards
Published standards over house conventions.
Every bespoke mechanism is one your HTTP library cannot already speak, so it becomes integration code that someone has to write and get subtly wrong. Where a standard exists, the API uses it.
| Concern | Standard |
|---|---|
| Errors | RFC 9457 Problem Details |
| Validation detail | RFC 6901 JSON Pointer |
| Identifiers | RFC 9562 UUIDv7 |
| Conditional writes | RFC 9110 ETag / If-Match |
| Custom headers | RFC 9651 Structured Fields |
| Webhook signing | RFC 9421 Message Signatures |
| Body integrity | RFC 9530 Content-Digest |
| Deprecation | RFC 9745 + RFC 8594 |
| Access tokens | RFC 9068 JWT profile |
| Timestamps | RFC 3339, always UTC |
The API reference is not public yet. If you are building an integration — ordering, stock, accounting, a franchise portal — ask and we will send you the spec. tender@shutthegoatup.com
Early access
Be one of the first shops on it.
No forms, no sales team. Tell us what you run, where you trade and what your till does now. The people building Tender read every email and reply themselves.