Skip to content
Tender

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.

Record a sale — safe to retryPUT
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.

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

Standards used by the Tender API
ConcernStandard
ErrorsRFC 9457 Problem Details
Validation detailRFC 6901 JSON Pointer
IdentifiersRFC 9562 UUIDv7
Conditional writesRFC 9110 ETag / If-Match
Custom headersRFC 9651 Structured Fields
Webhook signingRFC 9421 Message Signatures
Body integrityRFC 9530 Content-Digest
DeprecationRFC 9745 + RFC 8594
Access tokensRFC 9068 JWT profile
TimestampsRFC 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.