POST/v1/refusal-resolutions/batch-create
Resolve refused items
Records what a person on a till did about items the server refused it: wrote a sale off, or dismissed a write the world had moved past. The server records every item it refuses a device at the moment it refuses it, with the problem document it answered; this is the other half, so the back office can show which refusals are still owed an answer and who answered the rest.
A refused item sent again that lands needs nothing from here: the server sees the same
idempotency_key accepted and records the refusal as resubmitted itself.
An item is resolved once. The first resolution stands, and another for the same refused
key — sent twice, or racing the item landing — is answered unchanged. A device resolves
only its own refusals, and only a device may call this: an integration's refusals are its
own to keep, and the back office has no till to act from.
Each item carries its own idempotency_key and stands or falls on its own.
Request body application/json · RefusalResolutionBatchCreateRequest
-
itemsarray of RefusalResolutionBatchCreateItemRequired
≥ 1 items · ≤ 500 items
hlcHLCA hybrid logical clock stamp (Kulkarni et al., 2014): the recorder's wall clock in milliseconds in the high 48 bits and a counter in the low 16, a 64-bit integer written as its decimal digits in a string. A stamp is past 2^53, beyond the integers a JSON number holds exactly as most parsers read it, so I-JSON writes it as a string (RFC 7493 §2.2).
A device stamps every fact it records, as it records it, after every stamp it has made or pulled — every change on the feed carries one — so a fact recorded after seeing another ranks after it whatever either wall clock says. The stamp is when the fact happened. The server keeps it unless it is more than ten minutes ahead of the server's clock, more than thirty days behind it, or from before the device was paired. A fact is never refused for its stamp: one the server does not believe, cannot read or was not given is stamped as the server takes it, and flagged on what it dates — the order, the shift, the drawer or the shelf.
idempotency_keyItemIdempotencyKeyRequired
A key for one item in a batch, minted by the client when it queues the item and reused on every attempt to send it. The item, not the request, is the unit of work: a retried batch returns the stored result for every item already applied under its key. A fresh key per attempt defeats the whole mechanism.
Scoped per merchant, so keys need only be unique within a merchant's own traffic.
16–255 chars
member_idstring · uuidThe member signed on to the till who did it, when somebody was.
outcomestring · enumRequired
written_offis a sale whose money never reached the books, accounted for as lost.dismissedis a write the world had moved past, or one the server already had — nothing to write off.written_offdismissedreasonstringRequired
Why, in the words of the person who did it.
1–512 chars
refused_idempotency_keystringRequired
The key the refused item was sent under, as the device sent it — its
idempotency_key, or, on a collection whose items are facts, the fact's ownid, which is the key a fact is retried under. The field is named for the commoner of the two and holds either.1–255 chars
resolved_atstring · date-timeRequired
What the device's own clock read when they did it, kept as it said it. Nothing is decided by it: the resolution is dated by its stamp (
hlc).
Responses
-
200
The batch was processed.
resultssays what happened to each item.BatchResponse · application/json
-
resultsarray of BatchResultRequired
idstring · uuidThe resource the item created or changed. Absent when it failed.
idempotency_keystringRequired
The item's key, echoed so a result can be matched by key as well as position.
-
problemProblemNullable
Why the item failed, as an RFC 9457 problem. A
422means the item is malformed and will never succeed — drop it and show it to staff rather than retrying for ever. A409or a5xxmeans try again later.typestring · uriRequired
A stable URI identifying the problem kind. This is the field to branch on.
default about:blank
api_versionstringPresent on an
api-version-retiredorapi-version-unknownrefusal: theAPI-Versionthis server speaks (docs/decisions/0011).detailstringA human-readable explanation specific to this occurrence. Safe to show to a developer; never contains internal state, SQL, or stack traces.
instancestring · uri-referenceA URI reference identifying the specific occurrence.
-
invalid_paramsarray of InvalidParamPresent on
422responses. Lists every field that failed validation, rather than only the first, so a client can highlight an entire form in one pass.codestringStable machine-readable code for this failure, for clients that localise their own messages rather than showing
reason.pointerstringRequired
RFC 6901 JSON Pointer to the offending value within the request body. A pointer rather than a field name so that failures nested inside arrays — the third line item of an order — are addressable at all.
reasonstringRequired
Why this value was rejected, in human-readable form.
-
refusalsarray of Location refusalPresent on
409responses to a withdrawal refused by the state it depends on (deleting a location with tills still assigned, open orders, …). Lists every reason at once, each with how much stands in the way, so a client can say what to put right and link to it rather than walking a person through one refusal after another.kindstringRequired
A stable refusal kind —
devices,open_orders,drawer_sessions,bookingsorstock— that a client links to the screen that puts it right. Branch on this field, never on the surrounding prose.countintegerRequired
How many things stand in the way of the withdrawal, so a client can repeat the number rather than re-read prose.
≥ 1
statusintegerRequired
The HTTP status code, repeated here so the body is self-contained.
100–599
titlestringRequired
A short, human-readable summary of the problem kind.
trace_idstringThe OpenTelemetry trace id for this request. Quote it in a support ticket and the whole request can be pulled up.
statusstring · enumRequired
created— the item made a new resource.updated— it changed an existing one.unchanged— this key was already applied and the original outcome stands. A success, not an error: it is what a client retrying a batch it was unsure landed should expect, and it clears the item from its queue exactly ascreateddoes.failed— the item was refused, and nothing else in the batch was affected. Seeproblem.createdupdatedunchangedfailedversioninteger · int64The resource's version after the item. Absent when it failed.
-
-
400
The request could not be parsed. Malformed JSON, a bad query parameter, or a header that failed to decode, among them an
API-Versionlater than the server speaks or not a date (api-version-unknown, with the server's own inapi_version).Distinct from
422: a400never reached validation, soinvalid_paramsis absent. Retrying without changing the request will fail again, except that a request refused for itsAPI-Versiongoes once the server is updated. -
401
No credentials, or credentials that could not be verified — missing bearer token, malformed JWT, expired token, or a signature that failed against the issuer's JWKS.
Refresh the token and retry once. Repeated
401after a refresh means the device needs to re-enrol. -
403
Authenticated, but not permitted. The token is valid and its merchant was resolved, but the caller's role does not allow this operation, or the target belongs to a different merchant.
Cross-tenant access is reported as
403on a resource the caller may not see, and as404where even confirming existence would leak information. Retrying will not help. -
410
The request names an
API-Versionthis server no longer serves (docs/decisions/0011). Nothing in it was read.api_versionnames the version the server speaks: the client is behind, and sends the request again once it is updated. -
415
The request
Content-Typeis not supported. This API acceptsapplication/jsonon all mutations. -
422
The request parsed but failed validation.
invalid_paramslists every failing field with a JSON Pointer, not just the first, so a client can mark up a whole form in one pass rather than making the user fix errors one round trip at a time.Also returned when an
Idempotency-Keyis replayed with a different body, and when device-computed order totals disagree with the server's recomputation. -
429
Rate limit exceeded. Honour
Retry-Afterrather than applying your own backoff.A fleet of tills coming back online together after an outage is the realistic cause, which is exactly when uncoordinated client-side retry turns a brief outage into a sustained one. The limit is per process, and per site for what records trade there — a request spends its site's budget, and a till's own smaller one inside it — and per
(client, merchant)for everything else; noRateLimitfield is sent, so this response and itsRetry-Afterare the whole of the contract. -
500
Something failed on our side.
trace_ididentifies the request in our tracing — quote it in a support ticket.The body never carries internal detail: no SQL, no stack traces, no upstream error strings. Safe to retry with the same
Idempotency-Key. -
503
Temporarily unable to serve the request — a dependency is down or the service is shedding load.
Retry with the same
Idempotency-KeyafterRetry-After. This is always safe: a request that did take effect before the failure will be recognised as a replay.