{"openapi":"3.1.1","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"AimoPilot","version":"1.0.0","summary":"Open ordering gateway for AI purchasing agents.","description":"Any company's purchasing agent orders from the wholesaler carrying a signed mandate that says what it may buy; anything beyond the mandate needs a human sign-off. Start an order with POST /api/v1/intake: Dock checks the need for likely mistakes, asks the buyer at most two typed questions, offers option cards and quotes the chosen one; the lower-level search, availability and quote operations stay available for follow-up. Every refusal is `{ error: { code, rule, message, details } }` where `rule` names the check that refused. The same operations are available as MCP tools at /api/mcp. Discovery: /.well-known/api-catalog (RFC 9727), /.well-known/dock.json, /llms.txt."},"servers":[{"url":"https://0.0.0.0:8080"}],"externalDocs":{"description":"How to connect an agent","url":"https://0.0.0.0:8080/llms.txt"},"security":[{"mandate":[],"agentId":[]}],"tags":[{"name":"agent","description":"For buyer agents, with the organisation's signed mandate."},{"name":"approver","description":"For the human approver's phone and the demo screens. Not for agents: approving needs the approver's own passkey, session or link token."},{"name":"public","description":"Discovery, keys, the ledger's public view and live events."}],"paths":{"/api/v1":{"get":{"operationId":"index","summary":"List the endpoints","security":[],"tags":["public"],"x-dock-audience":"public","responses":{"200":{"description":"Endpoint index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}}}}},"/api/v1/requests":{"post":{"operationId":"openRequest","summary":"Open a purchase request","description":"The buyer's need in their own words and an optional need-by date. Every later call takes the returned requestId.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenRequestBody"}}}},"responses":{"201":{"description":"Request opened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestCreated"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/products":{"get":{"operationId":"searchProducts","summary":"Search product master data","description":"Local master data. Does not call the live price/availability service.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"requestId","in":"query","required":true,"schema":{"type":"string","minLength":1}},{"name":"q","in":"query","required":false,"description":"Words that must all appear in name, brand, category or attributes.","schema":{"type":"string"}},{"name":"category","in":"query","required":false,"schema":{"type":"string"}},{"name":"attribute","in":"query","required":false,"description":"Repeat for several; every product returned carries all of them.","schema":{"type":"array","items":{"type":"string"}},"style":"form","explode":true},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","exclusiveMinimum":0,"maximum":100}}],"responses":{"200":{"description":"Matching products.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProductList"}}}},"400":{"description":"Invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown requestId for this agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/availability":{"post":{"operationId":"checkAvailability","summary":"Live price and availability for a short list","description":"The partner's live service. At most 10 products per call; more is refused with rule live_lookup_limit.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AvailabilityBody"}}}},"responses":{"200":{"description":"Live offers (each with the ids of its signed claims), the products that cannot be quoted and why, and with includeClaims the signed claims as vc+jwt tokens.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferList"}}}},"400":{"description":"Invalid body, or too many products (rule live_lookup_limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown requestId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"This agent's hourly allowance of live lookups is used up (rule live_lookup_limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"The wholesaler's live service did not answer (code live_unavailable). Not a refusal: retry.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/quotes":{"post":{"operationId":"requestQuote","summary":"Get a signed fixed-price quote","description":"Quantities in packs. `substituteFor` marks a line that replaces a product the buyer could not get enough of. The response carries the signed quoteToken and a non-binding policy preview.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteBody"}}}},"responses":{"201":{"description":"Quote issued.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteCreated"}}}},"400":{"description":"Invalid lines or too many products.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown requestId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Not enough stock or no live offer (rule quote_availability); `details.available` says how many packs there are.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/orders":{"post":{"operationId":"placeOrder","summary":"Place an order against a quote","description":"The gate checks the order against the mandate. `{ requestId }` alone places the latest quote Dock signed for the request (the intake's choice, or POST /api/v1/quotes). `replaces` changes an earlier order: once this one is placed, Dock takes that one back if it still can. 201: committed with a signed confirmation. 202: waits for a human sign-off; do not retry. 422: refused, `error.rule` names the rule.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceOrderBody"}}}},"responses":{"201":{"description":"Committed within the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacedEnvelope"}}}},"202":{"description":"Waiting for a human sign-off. Do not retry; GET /api/v1/orders/{orderId}?wait=25 waits for the decision.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlacedEnvelope"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown requestId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Refused by the gate, or the quote token does not verify (rule quote_signature).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Refusal"}}}}}},"get":{"operationId":"listOrders","summary":"List this organisation's orders","description":"Newest first: status, total, delivery day, lines, who must sign and whether they have, and whether it can still be taken back (`canTakeBack`).","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"status","in":"query","required":false,"schema":{"type":"string","enum":["pending_approval","committed","declined","rejected","withdrawn","expired"]}},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","minimum":1,"maximum":50}}],"responses":{"200":{"description":"Orders.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"400":{"description":"Invalid query.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/orders/{orderId}":{"get":{"operationId":"getOrder","summary":"Read an order","description":"With `wait` (seconds, at most 55), the answer comes as soon as a person decides an order that waits for sign-off, or when the time is up (`decided: false`).","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string"}},{"name":"wait","in":"query","required":false,"description":"Wait up to this many seconds for a sign-off decision.","schema":{"type":"integer","minimum":0,"maximum":55}}],"responses":{"200":{"description":"The order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WaitedEnvelope"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such order for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/orders/{orderId}/timeline":{"get":{"operationId":"getOrderTimeline","summary":"An order's whole timeline","description":"The order from the buyer's words to now, one step per row: how the request was read, lookups, options and the choice, the signed quote, the gate's rules, the sign-off (who, how, after how long), the order, its receipt and the check of its signature, and what came after (taken back, a substitute, a backup, the receipt email). Each step names who or what acted (`actor.kind`: agent, person, system, wholesaler), marks the agent's own choices (`agentChoice`) and carries its ledger entry (`ledger.seq`, `ledger.hash`). The wholesaler's operations read the same timeline, with the kitchen's private memory left out.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The timeline.","content":{"application/json":{"schema":{"type":"object","properties":{"timeline":{"type":"object","properties":{"orderId":{"type":"string"},"status":{"type":"string"},"source":{"type":"string"},"steps":{"type":"array","items":{"type":"object","properties":{"seq":{"type":"number"},"at":{"type":"string"},"title":{"type":"string"},"actor":{"type":"object","properties":{"kind":{"type":"string"},"name":{"type":"string"}},"required":["kind","name"],"additionalProperties":{}},"agentChoice":{"type":"boolean"}},"required":["seq","at","title","actor","agentChoice"],"additionalProperties":{}}}},"required":["orderId","status","source","steps"],"additionalProperties":{}}},"required":["timeline"]}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such order for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/lookup":{"post":{"operationId":"lookupProducts","summary":"Look up products, prices and availability","description":"One call: products matching words, category, brand, claims, allergens to avoid, pack size and delivery day, inside the mandate, each with pack, claims, labels, allergens, origin, delivery reliability, carbon (product-group average) and whether it is the kind asked for (`match`), plus live price, stock, earliest delivery and order cut-off for at most 10 (metered). Opens a request when none is given.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupBody"}}}},"responses":{"200":{"description":"Products.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LookupResult"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown requestId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Live lookup allowance used up (rule live_lookup_limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/alternatives":{"post":{"operationId":"findAlternatives","summary":"Rank alternatives: good, better, best","description":"What can stand in for a product inside the mandate, ranked best, better, good, each with its trade-offs, live price and delivery, and what the gate would decide if it were ordered. Keeps the product's dietary claims; never an allergen the mandate or the buyer forbids.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlternativesBody"}}}},"responses":{"200":{"description":"Alternatives.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AlternativesResult"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"Unknown product or requestId.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"Live lookup allowance used up (rule live_lookup_limit).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/orders/{orderId}/withdraw":{"post":{"operationId":"withdrawOrder","summary":"Take back an order","description":"Takes back an order that is still pending_approval (it stops holding the weekly budget at once and the approver's screen shows it as withdrawn), or a committed order this agent placed alone inside its mandate, until the wholesaler's order cut-off. An order a person signed off is theirs to take back in the kitchen's store (409 take_back_needs_person); after the cut-off nothing can be (409 past_cutoff). `by` (who asked) is recorded as relayed by the agent, not verified. An order waiting for sign-off also stops holding the budget by itself when its quote's fixed price expires (status expired).","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"orderId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","maxLength":300},"by":{"type":"string","minLength":1,"maxLength":120}}}}}},"responses":{"200":{"description":"The withdrawn order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderEnvelope"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such order for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"A person signed it (take_back_needs_person), the cut-off passed (past_cutoff), another agent placed it (take_back_not_own), or it is decided (order_not_pending).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/approvals":{"get":{"operationId":"listPendingApprovals","summary":"Orders waiting for a human sign-off","description":"Never includes approval tokens. An agent (mandate) sees its own organisation's, an approver (signed-in phone, `dock_approver` cookie) their kitchen's, the operations desk everyone's; a stranger gets 401.","security":[],"tags":["public"],"x-dock-audience":"public","responses":{"200":{"description":"Pending approvals the caller may see.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalList"}}}},"401":{"description":"No mandate, approver session or operations session (rule data_access).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/approvals/{approvalId}":{"post":{"operationId":"decideApproval","summary":"A human approves or declines","description":"For the human approver, never for agents: approving needs the approver's passkey assertion (or, for approvers without a passkey, the one-time link token). The assertion is bound to this order's digest and this decision. The gate re-checks the order before committing. Not offered as an MCP tool.","security":[],"tags":["approver"],"x-dock-audience":"approver","parameters":[{"name":"approvalId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalDecisionBody"}}}},"responses":{"200":{"description":"Decided; the order is committed or declined.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalDecided"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No proof (rule approver_identity).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"Wrong token, wrong approver, or a passkey assertion that does not verify (rule approver_identity).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such approval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Already decided, or the re-check at sign-off refused the order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalRefused"}}}}}},"get":{"operationId":"getApprovalView","summary":"The sign-off screen's content","description":"What, how much, when, and why this person is asked, in plain words. No secrets. For the kitchen's approver (signed-in phone), its agent, the operations desk, or `?token=` from the one-time approval link; 404 for anyone else. With the approver cookie, `viewerIsApprover` says whether this phone may decline in one tap.","security":[],"tags":["approver"],"x-dock-audience":"approver","parameters":[{"name":"approvalId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"The approval in plain words.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalViewEnvelope"}}}},"404":{"description":"No such approval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/approvals/{approvalId}/challenge":{"post":{"operationId":"approvalChallenge","summary":"A passkey prompt for one decision","description":"Returns WebAuthn request options for startAuthentication(). The challenge is derived from the approval, the order's current digest, the decision and a fresh nonce; single use, five minutes.","security":[],"tags":["approver"],"x-dock-audience":"approver","parameters":[{"name":"approvalId","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChallengeBody"}}}},"responses":{"200":{"description":"Passkey request options.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChallengeOptions"}}}},"400":{"description":"decision is not approve or decline.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such approval, or its approver has no passkey.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Already decided (code approval_not_pending).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/approvals/{approvalId}/link":{"get":{"operationId":"approvalLink","summary":"URL and QR code of the sign-off screen","description":"For other screens. The URL carries no secret. The same readers as the sign-off screen; 404 for anyone else.","security":[],"tags":["approver"],"x-dock-audience":"approver","parameters":[{"name":"approvalId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Link and QR code.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalLink"}}}},"404":{"description":"No such approval.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/join":{"post":{"operationId":"beginJoin","summary":"Join by QR code, step 1: the passkey prompt","description":"Who the visitor is about to become (a fictional kitchen's purchasing manager) and the WebAuthn creation options. Nothing is created until POST /api/v1/join/verify.","security":[],"tags":["approver"],"x-dock-audience":"approver","responses":{"200":{"description":"Identity and passkey prompt.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinStart"}}}},"503":{"description":"Too many demo kitchens right now (code sandbox_full).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/join/verify":{"post":{"operationId":"completeJoin","summary":"Join by QR code, step 2: register the passkey","description":"Creates the visitor's kitchen (its own organisation key, signed mandate and buyer agent) with this phone's passkey as its only approver, and sets the HttpOnly `dock_approver` cookie.","security":[],"tags":["approver"],"x-dock-audience":"approver","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinVerifyBody"}}}},"responses":{"201":{"description":"Joined; the approver cookie is set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Joined"}}}},"400":{"description":"No passkey response.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"Joining without a passkey is switched off (code demo_tap_disabled).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"Too many demo kitchens right now (code sandbox_full).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/approver":{"get":{"operationId":"approverHome","summary":"The signed-in approver's home","description":"Who they are, what their agent may do alone, what waits for their sign-off and what was decided recently.","security":[{"approverSession":[]}],"tags":["approver"],"x-dock-audience":"approver","responses":{"200":{"description":"Home screen content.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproverHome"}}}},"401":{"description":"This phone has not joined a demo kitchen (no approver cookie); rule approver_identity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/approver/demo-order":{"post":{"operationId":"approverDemoOrder","summary":"DEMO: start this kitchen's agent on the 1,000 L milk order","description":"The visitor kitchen's own agent opens the request, quotes and orders; the order exceeds its limit and waits for this approver's sign-off.","security":[{"approverSession":[]}],"tags":["approver"],"x-dock-audience":"approver","responses":{"201":{"description":"The agent placed the order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoOrderStarted"}}}},"401":{"description":"This phone has not joined a demo kitchen (no approver cookie); rule approver_identity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/events":{"get":{"operationId":"events","summary":"Live events (server-sent events)","description":"A text/event-stream of approval_requested, approval_approved, approval_declined, approval_withdrawn, approval_expired, order_committed, order_rejected and order_withdrawn (a committed order taken back before the cut-off). Each event's `data` is one DockEvent; refetch details on an event. Reconnects resume from Last-Event-ID. A comment line every 15 s keeps idle connections open. Unfiltered, the operations desk gets every event in full and anyone else an aggregated stream (type, time, amount and a generic sentence: no kitchen, order or approval ids). orgId= needs that kitchen's agent, approver or operations (the built-in demo kitchens are open); approvalId= needs a reader of that approval (or token=).","security":[],"tags":["public"],"x-dock-audience":"public","parameters":[{"name":"scope","in":"query","required":false,"description":"mine: this phone's kitchen only (needs the approver cookie).","schema":{"type":"string","enum":["mine"]}},{"name":"approvalId","in":"query","required":false,"description":"Only events of one approval.","schema":{"type":"string"}},{"name":"token","in":"query","required":false,"description":"With approvalId: the one-time link token, for a phone without the approver cookie.","schema":{"type":"string"}},{"name":"orgId","in":"query","required":false,"description":"Only events of one organisation.","schema":{"type":"string"}},{"name":"since","in":"query","required":false,"description":"Replay events after this id (as Last-Event-ID).","schema":{"type":"string"}}],"responses":{"200":{"description":"Event stream; each event's data is a DockEvent (aggregated for the public).","content":{"text/event-stream":{"schema":{"$ref":"#/components/schemas/DockEvent"}}}},"401":{"description":"This phone has not joined a demo kitchen (no approver cookie); rule approver_identity.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"Another kitchen's events (rule data_access).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such approval for this caller.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/sandboxes":{"get":{"operationId":"listSandboxes","summary":"Live demo kitchens","description":"For the big screen. No keys, no mandate tokens. Anyone: who is here (kitchen, city, approver, times). The operations desk also gets ids and limits.","security":[],"tags":["approver"],"x-dock-audience":"approver","responses":{"200":{"description":"Demo kitchens.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SandboxList"}}}}}}},"/api/v1/sandboxes/{sandboxId}/demo-order":{"post":{"operationId":"sandboxDemoOrder","summary":"DEMO: the big screen starts a visitor kitchen's agent","description":"Same as POST /api/v1/approver/demo-order for a given kitchen. Creates at most a sign-off request; it never approves. Only from the visitor's own phone or the operations desk's screen.","security":[],"tags":["approver"],"x-dock-audience":"approver","parameters":[{"name":"sandboxId","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"201":{"description":"The agent placed the order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DemoOrderStarted"}}}},"401":{"description":"Not signed in (rule data_access).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"Another visitor's kitchen (rule data_access).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such demo kitchen.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/intake":{"post":{"operationId":"openIntake","summary":"Start an order from a structured need","description":"The recommended way to start. AimoPilot checks the need for likely mistakes (against the kitchen's own history), asks at most two typed questions that its memory cannot answer, and offers option cards with pros and cons. Every reply is `{ step }`: its `state` says what happens next and `next` names the call. Opens a purchase request; `step.requestId` is its id.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeBody"}}}},"responses":{"201":{"description":"The first step.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeStepEnvelope"}}}},"400":{"description":"Invalid need.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"AimoPilot cannot tell the amount (codes amount_unknown, nothing_to_repeat). A product the catalogue does not have, or a product group outside the mandate, is not an error: the step is no_options with the reason in notes.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/agent/interpret":{"post":{"operationId":"interpretRequest","summary":"Free text to a structured need","description":"Optional helper before POST /api/v1/intake: a language model reads the buyer's words (English, Finnish or Swedish) and fills only what was said; plain code checks every field, copies the buyer's own words, and leaves anything unstated empty so the intake can ask or consult memory. The model never decides anything: the intake and the gate do. With `earlier` (the kitchen's earlier requests) a follow-up such as \"same again but 200 litres\" is resolved against one of them and `basedOn` names it. Each caller has an hourly allowance of model calls; the same text for the same kitchen on the same day is answered from a cache (`cached: true`).","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/InterpretBody"}}}},"responses":{"200":{"description":"The structured need, ready for POST /api/v1/intake.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Interpretation"}}}},"400":{"description":"Empty or too long text, or an invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"not_a_purchase_request (the text does not ask to buy anything), or need_incomplete (details.missing lists item and/or quantity; details.partial holds what was understood).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"429":{"description":"This caller's hourly allowance of model calls is used up (interpret_quota_exceeded); see Retry-After.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"502":{"description":"The model failed or returned something plain code refused (interpret_failed, interpret_invalid_output).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"503":{"description":"No model is configured or the provider is unavailable (model_unavailable).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"504":{"description":"The model did not answer in time (interpret_timeout).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/intake/{requestId}":{"get":{"operationId":"getIntake","summary":"The current step of an intake","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"requestId","in":"path","required":true,"description":"requestId from POST /api/v1/intake.","schema":{"type":"string"}}],"responses":{"200":{"description":"The current step.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeStepEnvelope"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such intake for this agent.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/intake/{requestId}/concerns":{"post":{"operationId":"respondToConcerns","summary":"The buyer confirms or corrects a likely mistake","description":"Only in state needs_confirmation. For each open concern, `choice` is \"confirm\" (when the concern is confirmable) or one of its correction ids. The answer must come from the human buyer. `by` names the person whose answer the agent relays; it is written to the audit ledger.","tags":["agent"],"x-dock-audience":"agent","x-dock-human-input":{"required":true,"from":"buyer","how":"the agent relays the buyer's own answer, or gives the buyer the step's answerUrl to answer on Dock's page; never answer for them"},"parameters":[{"name":"requestId","in":"path","required":true,"description":"requestId from POST /api/v1/intake.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConcernResponseBody"}}}},"responses":{"200":{"description":"The next step.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeStepEnvelope"}}}},"400":{"description":"Invalid body, or an id that is not one of the offered choices.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such intake for this agent (code intake_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The intake is in another state (code intake_wrong_state; `details.state` says which), or the concern or question is not open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/intake/{requestId}/answers":{"post":{"operationId":"answerQuestions","summary":"The buyer answers the clarifying questions","description":"In state needs_input (or options_ready, to change an answer memory pre-selected). One optionId per question; `remember: true` stores it as the kitchen's stated preference. The answer must come from the human buyer. `by` names the person whose answer the agent relays; it is written to the audit ledger.","tags":["agent"],"x-dock-audience":"agent","x-dock-human-input":{"required":true,"from":"buyer","how":"the agent relays the buyer's own answer, or gives the buyer the step's answerUrl to answer on Dock's page; never answer for them"},"parameters":[{"name":"requestId","in":"path","required":true,"description":"requestId from POST /api/v1/intake.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AnswersBody"}}}},"responses":{"200":{"description":"The next step.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeStepEnvelope"}}}},"400":{"description":"Invalid body, or an id that is not one of the offered choices.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such intake for this agent (code intake_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The intake is in another state (code intake_wrong_state; `details.state` says which), or the concern or question is not open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/intake/{requestId}/choice":{"post":{"operationId":"chooseOption","summary":"Choose an option card and get its signed quote","description":"Only in state options_ready. The wholesaler quotes the chosen option at a fixed price; the reply is state quoted with the quote, the gate's preview and the body for POST /api/v1/orders. `by` names the person whose answer the agent relays; it is written to the audit ledger.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"requestId","in":"path","required":true,"description":"requestId from POST /api/v1/intake.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChoiceBody"}}}},"responses":{"200":{"description":"Quoted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IntakeStepEnvelope"}}}},"400":{"description":"Invalid body, or an id that is not one of the offered choices.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such intake for this agent, or no such option (code option_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The intake is in another state (code intake_wrong_state; `details.state` says which), or the concern or question is not open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/kitchen":{"get":{"operationId":"getKitchen","summary":"The kitchen's memory","description":"What AimoPilot remembers about the buyer organisation: order history, usual amounts per product family, rules people stated, and patterns learned from the history.","tags":["agent"],"x-dock-audience":"agent","responses":{"200":{"description":"Kitchen memory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/KitchenEnvelope"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/offers":{"get":{"operationId":"listOffers","summary":"The wholesaler's offers to this kitchen","description":"What the wholesaler's selling agent offered this kitchen: a new, seasonal or cheaper like-for-like product, with the reasons and their figures, the price per pack and until when it holds. Each offer is a signed W3C Verifiable Credential (compact JWS, `typ: vc+jwt`, type `DockProductOffer`, Ed25519) addressed to the buyer organisation: verify `token` offline against /.well-known/jwks.json. An offer is never an order. Open offers only, unless `all=true`.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"all","in":"query","required":false,"description":"true: also answered and expired offers.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"Offers, newest first, and what the kitchen muted.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferList"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/offers/{offerId}/accept":{"post":{"operationId":"acceptOffer","summary":"Accept an offer: start an ordinary order","description":"Buys nothing: it opens an ordinary intake for the offered product (the mistake check, option cards, a fixed-price quote the wholesaler signs, the mandate gate and its sign-off). Continue from the returned step exactly as after POST /api/v1/intake. Only on the buyer's own yes.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"offerId","in":"path","required":true,"description":"offerId from GET /api/v1/offers.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferAcceptBody"}}}},"responses":{"201":{"description":"The ordinary order it started, as an intake step.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferAccepted"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such offer for this organisation (code offer_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The offer is closed, expired, does not verify, or the kitchen's own rules do not allow it (codes offer_closed, offer_invalid, offer_not_for_kitchen).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/offers/{offerId}/decline":{"post":{"operationId":"declineOffer","summary":"Decline an offer","description":"Not wanted. The product is not offered to this kitchen again for 30 days.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"offerId","in":"path","required":true,"description":"offerId from GET /api/v1/offers.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferAnswerBody"}}}},"responses":{"200":{"description":"The declined offer.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferDeclined"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such offer for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"The offer is no longer open.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/offers/mute":{"post":{"operationId":"muteOffers","summary":"Stop a kind of offer, or every offer","description":"scope `product`, `family` (a product family), `reason` (new_in_assortment, in_season, cheaper, more_reliable, lower_carbon, new_to_kitchen) or `all` (offers off). With offerId the value comes from that offer; `undo: true` lifts it. The selling agent reads this before it picks anything.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferMuteBody"}}}},"responses":{"200":{"description":"What the kitchen muted now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OfferPrefsReply"}}}},"400":{"description":"Invalid body, or nothing to mute.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such offer for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/menus/seasonal":{"post":{"operationId":"planSeasonalMenu","summary":"Plan a menu around what is in season","description":"Two or three dishes built on an ingredient in season in Finland now that the assortment has (a public season calendar, with its source; searched in master data). Per ingredient: the need for the covers, what the kitchen has on hand (an estimate from its own order history, or what its person said; each figure with its source; nothing assumed when nothing is known), what is bought (the shortfall, in the smallest pack that covers it, with what is left over), and the gate's preview of the whole menu. Buys nothing; order with POST /api/v1/menus/{planId}/order.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuSeasonalBody"}}}},"responses":{"200":{"description":"The plan: anchor, dishes, lines (need, onHand, toBuy, product, leftover), gate preview.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/menus/event":{"post":{"operationId":"planEventMenu","summary":"Plan a booking at short notice","description":"A menu for a booking (for example tomorrow). Every ingredient is checked with the wholesaler for the day, for the dishes' products only: `cannotArrive` says which, why (an order cut-off passed at a time, out of stock, no delivery that day) and the earliest day. `options[lineId]` are substitutes through recipe logic (another ingredient doing the same job in the dish at an adjusted amount, a change of technique, a replacement dish), each verified (in the assortment, arrives by the day, the kitchen's rules and nutrition rules, the mandate), ranked best, better, good, with its trade-off and the gate's word on it (`signOff`). Buys nothing.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuEventBody"}}}},"responses":{"200":{"description":"The plan, what cannot arrive, the verified options.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuPlan"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Not a request to plan food (code not_a_menu).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/menus/ideas":{"post":{"operationId":"menuIdeas","summary":"Dish ideas this kitchen can buy","description":"Three or four dishes that fit the buyer's words, each with a reason, checked before it is offered against master data and the kitchen's rules (its product groups, allergens and claims, nutrition rules, sold at all): dishes it can buy in full first; one that it cannot names the ingredients and why. One model call; no live price.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuIdeasBody"}}}},"responses":{"200":{"description":"The ideas, with what each needs that the kitchen cannot buy.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuIdeas"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/menus/dish":{"post":{"operationId":"planDish","summary":"A dish in three groups: on hand, to order, cannot get","description":"The picked idea (ideasId, dishId) planned as the dish, or an existing plan (planId): every ingredient with its amount for the covers in exactly one group (on hand with its basis; to order with product, packs, live price and day; cannot get with the reason as a code), the total to order, the price per cover only when the plan is complete, and `complete`. Buys nothing.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuDishBody"}}}},"responses":{"200":{"description":"The dish in three groups.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuDish"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such ideas or plan.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/menus/{planId}/order":{"post":{"operationId":"orderMenu","summary":"Order a planned menu","description":"Every line being bought on one signed fixed-price quote, placed through the mandate gate: committed, or pending_approval for a named person. An ingredient that cannot arrive is replaced by the option in `choices` (the buyer's pick), its main product declared as a substitute so the gate judges it; without choices, only an option inside the kitchen's preferences is taken. Only on the buyer's own yes.","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"planId","in":"path","required":true,"description":"planId from POST /api/v1/menus/seasonal or /event.","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuOrderBody"}}}},"responses":{"201":{"description":"The sheet's lines and the order.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/MenuOrdered"}}}},"400":{"description":"Invalid body.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such plan for this agent, or no such option (codes plan_not_found, option_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Nothing on the plan can be ordered (code menu_nothing_to_order).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"422":{"description":"Refused by the gate; the error names the rule.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/watch":{"get":{"operationId":"getWatch","summary":"What may not arrive, and how recurring orders drift","description":"The kitchen agent's watch over its recurring orders and orders not yet delivered. `findings`: a product that cannot come for its day, with good/better/best substitutes (each with its trade-off in figures and `insidePreferences`: may be ordered without sign-off) and the signed availability claim (vc+jwt, verify against /.well-known/jwks.json; `simulated` when staged for a demo). `standing`: the weekly pattern learned from past orders (weekday, usual amount and unit price, the weeks it rests on), drift (portions, cost) and auto-renew. `renewals`: placed alone, sent for sign-off, or held with the question. `fresh=true` checks live now (one call for at most ten products).","tags":["agent"],"x-dock-audience":"agent","parameters":[{"name":"fresh","in":"query","required":false,"description":"true: run the watch now.","schema":{"type":"string","enum":["true","false"]}}],"responses":{"200":{"description":"The watch for this kitchen.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Watch"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/watch/answer":{"post":{"operationId":"answerWatch","summary":"Answer a watch finding or a held renewal","description":"findingId with `switch` (the best substitute, or `productId`) or `keep`; renewalId with `renew_new`, `usual` or `skip`. A switch or renewal is an ordinary order through the gate: committed within the mandate, otherwise it waits for the person the mandate names. Only on the buyer's own answer.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchAnswerBody"}}}},"responses":{"200":{"description":"What happened.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WatchAnswered"}}}},"400":{"description":"Invalid body or choice.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such finding or renewal for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"Already decided, or no substitute can come.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/watch/auto-renew":{"post":{"operationId":"setAutoRenew","summary":"Switch auto-renew for a recurring order","description":"The kitchen's explicit choice, recorded with who made it; off by default. On: placed before the cut-off when nothing drifted, through the gate; held and asked when something did.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoRenewBody"}}}},"responses":{"200":{"description":"The setting now.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AutoRenew"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No recurring order for that family (code standing_not_found).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/watch/backup":{"post":{"operationId":"lineUpBackup","summary":"Line up a backup for a product with a poor delivery record","description":"Only for an order's products whose delivery record is below the line (the wholesaler's delivery history, e.g. \"has arrived as ordered about 7 times in 10\"; never a forecast; a product with no figure is never flagged). Without `choice`: up to three alternatives, good/better/best, each with its own record, the trade-off in figures and the gate's preview. With `choice` (an alternative's productId, `best` for the best inside the kitchen's preferences, or `none`): recorded with who chose it. Buys nothing now: if the product later cannot come, the backup is ordered through the gate by itself. Quote and order results carry `deliveryRecords` for such lines.","tags":["agent"],"x-dock-audience":"agent","requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupBody"}}}},"responses":{"200":{"description":"The alternatives, or the backup recorded.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BackupResult"}}}},"400":{"description":"Not one of the alternatives.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"401":{"description":"No mandate, a bad signature, an expired mandate or no agent id. `error.rule` names the check.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"403":{"description":"The agent id does not match the mandate.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"404":{"description":"No such order or backup for this organisation.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}},"409":{"description":"None inside the kitchen's preferences for best, or already used.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorBody"}}}}}}},"/api/v1/ledger":{"get":{"operationId":"getLedger","summary":"Audit trail","description":"Hash-chained entries, the chain verified in full, and one plain-language line per step. Verification and head are public; entries only for their own organisation's agent (mandate) or approver (signed-in phone), and all of them for the operations desk. A stranger gets an empty `entries`.","security":[],"tags":["public"],"x-dock-audience":"public","parameters":[{"name":"requestId","in":"query","required":false,"schema":{"type":"string"}},{"name":"orderId","in":"query","required":false,"schema":{"type":"string"}},{"name":"view","in":"query","required":false,"schema":{"type":"string","enum":["buyer","wholesaler"]}}],"responses":{"200":{"description":"Ledger view. view=wholesaler: what was asked, claimed, committed and who signed, without the buyer's kitchen memory.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerView"}}}}}}},"/api/v1/keys":{"get":{"operationId":"getKeys","summary":"Wholesaler public keys (JWKS)","description":"RFC 7517 JWK Set with Ed25519 keys (RFC 8037) to verify quotes and order confirmations offline. Same document as /.well-known/jwks.json.","security":[],"tags":["public"],"x-dock-audience":"public","responses":{"200":{"description":"JWK Set.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Jwks"}}}}}}},"/api/v1/openapi.json":{"get":{"operationId":"getOpenApi","summary":"This document","security":[],"tags":["public"],"x-dock-audience":"public","responses":{"200":{"description":"OpenAPI 3.1 document.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OpenApiDocument"}}}}}}}},"components":{"securitySchemes":{"mandate":{"type":"http","scheme":"bearer","bearerFormat":"mandate+jwt (Ed25519 JWS signed by the buyer organisation)","description":"The buyer organisation's signed mandate. Send `Authorization: Bearer <jws>`; `Authorization: Mandate <jws>` is accepted too."},"agentId":{"type":"apiKey","in":"header","name":"Dock-Agent-Id","description":"The agent id the mandate names (its `sub` claim)."},"approverSession":{"type":"apiKey","in":"cookie","name":"dock_approver","description":"HttpOnly cookie set by POST /api/v1/join/verify on the approver's phone. It identifies the phone; approving still needs the passkey."}},"schemas":{"Error":{"type":"object","properties":{"code":{"type":"string","description":"Machine-readable error code, e.g. mandate_expired, quote_insufficient_stock, policy_rejected."},"rule":{"description":"The named check that refused, e.g. weekly_budget, live_lookup_limit, mandate_signature.","type":"string"},"message":{"type":"string","description":"Plain-language reason."},"details":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["code","message"],"additionalProperties":{}},"ErrorBody":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"}},"required":["error"],"additionalProperties":{}},"PurchaseRequest":{"type":"object","properties":{"requestId":{"type":"string"},"orgId":{"type":"string"},"agentId":{"type":"string"},"mandateId":{"type":"string"},"text":{"type":"string"},"neededBy":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["requestId","orgId","agentId","mandateId","text","createdAt"],"additionalProperties":{}},"Product":{"type":"object","properties":{"id":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"brand":{"type":"string","minLength":1},"category":{"type":"string","minLength":1},"unit":{"type":"string","enum":["L","kg","pcs"]},"packSize":{"type":"number","exclusiveMinimum":0},"attributes":{"type":"array","items":{"type":"string"}},"allergens":{"type":"array","items":{"type":"string"}},"certificates":{"type":"array","items":{"type":"string"}},"deliveryReliability":{"type":"number","minimum":0,"maximum":1},"carbonKgPerUnit":{"type":"number","minimum":0},"source":{"type":"string","enum":["seed","valio"]},"names":{"type":"object","properties":{"en":{"type":"string"},"fi":{"type":"string"},"sv":{"type":"string"}}},"vendor":{"type":["string","null"]},"categoryCode":{"type":"string"},"salesUnit":{"type":"string"},"baseUnit":{"type":"string"},"lotSize":{"type":"number","exclusiveMinimum":0},"packContent":{"type":"object","properties":{"perSalesUnit":{"type":"object","properties":{"L":{"type":"number"},"kg":{"type":"number"}}},"baseUnitsPerSalesUnit":{"type":["number","null"]},"certain":{"type":"boolean"},"certainMeasures":{"type":"array","items":{"type":"string","enum":["L","kg"]}},"issues":{"type":"array","items":{"type":"string"}},"label":{"type":"string"}},"required":["perSalesUnit","baseUnitsPerSalesUnit","certain","certainMeasures","issues","label"]},"allergenInfo":{"type":"object","properties":{"declared":{"type":"boolean"},"contains":{"type":"array","items":{"type":"string"}},"mayContain":{"type":"array","items":{"type":"string"}},"freeFrom":{"type":"array","items":{"type":"string"}},"byNature":{"type":"object","properties":{"allergens":{"type":"array","items":{"type":"string"}},"basis":{"type":"string"}},"required":["allergens","basis"]}},"required":["declared","contains","mayContain","freeFrom"]},"deliveryReliabilityKnown":{"type":"boolean"},"carbonKnown":{"type":"boolean"},"carbonGroupAvgKgCo2ePerKg":{"type":["number","null"]},"temperatureClass":{"anyOf":[{"type":"string","enum":["ambient","chilled","frozen"]},{"type":"null"}]},"countryOfOrigin":{"type":["string","null"]},"substitutes":{"type":"array","items":{"type":"string"}},"kinds":{"type":"array","items":{"type":"string"}},"nutrition":{"type":"object","properties":{"per":{"type":"string","enum":["100g","100ml"]},"values":{"type":"object","propertyNames":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose","calcium","iron","vitamin_d","vitamin_b12"]},"additionalProperties":{"type":"number","minimum":0}},"lessThan":{"type":"array","items":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose","calcium","iron","vitamin_d","vitamin_b12"]}}},"required":["per","values"]},"deposits":{"type":"array","items":{"type":"object","properties":{"material":{"type":"string"},"quantity":{"type":"number","exclusiveMinimum":0}},"required":["material","quantity"]}},"vatRatePct":{"type":"number","minimum":0},"texts":{"type":"object","propertyNames":{"type":"string","enum":["ingredients","storage","usage","disposal","about"]},"additionalProperties":{"type":"object","properties":{"en":{"type":"string"},"fi":{"type":"string"}}}},"mainIngredient":{"type":"object","properties":{"names":{"type":"object","properties":{"en":{"type":"string"},"fi":{"type":"string"},"sv":{"type":"string"}}},"pct":{"type":"number"},"origins":{"type":"array","items":{"type":"object","properties":{"stage":{"type":"string"},"country":{"type":"string"}},"required":["stage","country"]}}},"required":["names","origins"]},"origins":{"type":"array","items":{"type":"string"}},"fish":{"type":"array","items":{"type":"object","properties":{"species":{"type":"string"},"latin":{"type":"string"},"production":{"type":"string","enum":["aquaculture","marine_fishery","inland_fishery"]},"methods":{"type":"array","items":{"type":"string"}},"areas":{"type":"array","items":{"type":"string"}}},"required":["species","methods","areas"]}},"storageTempC":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"}}}},"required":["id","name","brand","category","unit","packSize","attributes","allergens","certificates","deliveryReliability","carbonKgPerUnit"]},"Offer":{"type":"object","properties":{"productId":{"type":"string"},"unitPriceCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live price per pack, euro cents ex VAT."},"availablePacks":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Packs in stock right now."},"deliveryDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$","description":"Earliest possible delivery."}},"required":["productId","unitPriceCents","availablePacks","deliveryDate"],"additionalProperties":{}},"RuleResult":{"type":"object","properties":{"rule":{"type":"string","enum":["mandate_agent","mandate_window","quote_binding","quote_expiry","quote_replay","order_matches_quote","category_allowed","required_attributes","forbidden_allergens","nutrition_rules","required_certificates","recall_hold","delivery_deadline","substitution_rule","substitute_price","weekly_budget","per_order_limit","order_splitting","human_confirmation","approver_authority"]},"outcome":{"type":"string","enum":["pass","escalate","fail"]},"reason":{"type":"string"},"details":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["rule","outcome","reason"],"additionalProperties":{}},"Decision":{"type":"object","properties":{"outcome":{"type":"string","enum":["auto_approve","needs_human_approval","reject"]},"totalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rules":{"type":"array","items":{"$ref":"#/components/schemas/RuleResult"},"description":"Every rule of the gate with its result, so a refusal names exactly what fired."},"reasons":{"type":"array","items":{"type":"string"}},"approver":{"description":"Present when outcome is needs_human_approval.","type":"object","properties":{"role":{"type":"string"},"name":{"type":"string"},"maxApprovalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["role","name","maxApprovalCents"],"additionalProperties":{}}},"required":["outcome","totalCents","rules","reasons"],"additionalProperties":{}},"QuoteLine":{"type":"object","properties":{"productId":{"type":"string","minLength":1},"name":{"type":"string"},"category":{"type":"string"},"unit":{"type":"string","enum":["L","kg","pcs"]},"packSize":{"type":"number","exclusiveMinimum":0},"attributes":{"type":"array","items":{"type":"string"}},"allergens":{"type":"array","items":{"type":"string"}},"allergensDeclared":{"type":"boolean"},"allergensByNature":{"type":"object","properties":{"allergens":{"type":"array","items":{"type":"string"}},"basis":{"type":"string"}},"required":["allergens","basis"]},"ingredients":{"type":"string","maxLength":1000},"certificates":{"type":"array","items":{"type":"string"}},"quantity":{"type":"number","exclusiveMinimum":0},"unitPriceCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"lineTotalCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"deliveryDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"substituteFor":{"type":"object","properties":{"productId":{"type":"string","minLength":1},"name":{"type":"string"},"category":{"type":"string"},"unit":{"type":"string","enum":["L","kg","pcs"]},"packSize":{"type":"number","exclusiveMinimum":0},"attributes":{"type":"array","items":{"type":"string"}},"unitPriceCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"nutrition":{"type":"object","properties":{"per":{"type":"string","enum":["100g","100ml"]},"values":{"type":"object","propertyNames":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose","calcium","iron","vitamin_d","vitamin_b12"]},"additionalProperties":{"type":"number","minimum":0}},"lessThan":{"type":"array","items":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose","calcium","iron","vitamin_d","vitamin_b12"]}}},"required":["per","values"]}},"required":["productId","name","category","unit","packSize","attributes","unitPriceCents"]},"nutrition":{"type":"object","properties":{"per":{"type":"string","enum":["100g","100ml"]},"values":{"type":"object","propertyNames":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose","calcium","iron","vitamin_d","vitamin_b12"]},"additionalProperties":{"type":"number","minimum":0}},"lessThan":{"type":"array","items":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose","calcium","iron","vitamin_d","vitamin_b12"]}}},"required":["per","values"]},"countryOfOrigin":{"type":"string"},"taxRatePct":{"type":"number","minimum":0},"taxSource":{"type":"string","enum":["price_service","product_file"]},"listUnitPriceCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"deposit":{"type":"object","properties":{"perPackCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"totalCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"items":{"type":"array","items":{"type":"object","properties":{"material":{"type":"string"},"kind":{"type":"string"},"quantity":{"type":"number","exclusiveMinimum":0},"unitCents":{"anyOf":[{"type":"integer","minimum":0,"maximum":9007199254740991},{"type":"null"}]}},"required":["material","kind","quantity","unitCents"]}},"unknown":{"type":"array","items":{"type":"string"}}},"required":["perPackCents","totalCents","items"]}},"required":["productId","name","category","unit","packSize","attributes","allergens","certificates","quantity","unitPriceCents","lineTotalCents","deliveryDate"]},"Quote":{"type":"object","properties":{"quoteId":{"type":"string","minLength":1},"buyerOrgId":{"type":"string","minLength":1},"agentId":{"type":"string","minLength":1},"mandateId":{"type":"string","minLength":1},"requestId":{"type":"string","minLength":1},"currency":{"type":"string","const":"EUR"},"lines":{"minItems":1,"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"totalCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"issuedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"expiresAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"expiresBecause":{"type":"string","enum":["ttl","price_validity","order_cutoff"]},"evidence":{"type":"array","items":{"type":"object","properties":{"claimId":{"type":"string","minLength":1},"type":{"type":"string","enum":["price","availability","origin"]},"productId":{"type":"string","minLength":1},"sha256":{"type":"string","pattern":"^[0-9a-f]{64}$"}},"required":["claimId","type","productId","sha256"]}},"goodsCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"depositCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"vat":{"type":"object","properties":{"byRate":{"type":"array","items":{"type":"object","properties":{"ratePct":{"type":["number","null"]},"baseCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"vatCents":{"anyOf":[{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},{"type":"null"}]}},"required":["ratePct","baseCents","vatCents"]}},"vatCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"incomplete":{"type":"boolean"}},"required":["byRate","vatCents","incomplete"]}},"required":["quoteId","buyerOrgId","agentId","mandateId","requestId","currency","lines","totalCents","issuedAt","expiresAt"]},"QuoteResult":{"type":"object","properties":{"quote":{"$ref":"#/components/schemas/Quote"},"quoteToken":{"type":"string","description":"The quote as a compact JWS signed by the wholesaler (typ quote+jws). Pass it unchanged to place an order."},"policyPreview":{"description":"What the gate would decide on this quote right now. Not binding.","$ref":"#/components/schemas/Decision"}},"required":["quote","quoteToken","policyPreview"],"additionalProperties":{}},"Order":{"type":"object","properties":{"orderId":{"type":"string"},"requestId":{"type":"string"},"orgId":{"type":"string"},"orgName":{"type":"string"},"agentId":{"type":"string"},"mandateId":{"type":"string"},"quoteId":{"type":"string"},"status":{"type":"string","enum":["pending_approval","committed","rejected","declined","withdrawn","expired"]},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"totalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"decision":{"$ref":"#/components/schemas/Decision"},"approval":{"type":"object","properties":{"approvalId":{"type":"string"},"approver":{"type":"object","properties":{"role":{"type":"string"},"name":{"type":"string"},"maxApprovalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["role","name","maxApprovalCents"],"additionalProperties":{}},"status":{"type":"string","enum":["pending","approved","declined","withdrawn","expired"]},"orderDigest":{"type":"string"},"requestedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"decidedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"note":{"type":"string"}},"required":["approvalId","approver","status","orderDigest","requestedAt"],"additionalProperties":{}},"confirmation":{"description":"Present once committed: compact JWS (typ order+jws) signed by the wholesaler. Verify it with the keys at /.well-known/jwks.json.","type":"string"},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"updatedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["orderId","requestId","orgId","orgName","agentId","mandateId","quoteId","status","lines","totalCents","decision","createdAt","updatedAt"],"additionalProperties":{}},"LedgerEntry":{"type":"object","properties":{"seq":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"type":{"type":"string"},"actor":{"type":"string"},"summary":{"type":"string"},"requestId":{"type":"string"},"orderId":{"type":"string"},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"prevHash":{"type":"string"},"hash":{"type":"string"}},"required":["seq","at","type","actor","summary","data","prevHash","hash"],"additionalProperties":{}},"LedgerVerification":{"type":"object","properties":{"ok":{"type":"boolean"},"length":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"head":{"anyOf":[{"type":"object","properties":{"seq":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hash":{"type":"string"}},"required":["seq","hash"]},{"type":"null"}]},"brokenAt":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"problem":{"type":"string"},"message":{"type":"string"}},"required":["ok"],"additionalProperties":{}},"CatalogStats":{"type":"object","properties":{"searches":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"liveCalls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"productsPriced":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["searches","liveCalls","productsPriced"],"additionalProperties":{}},"Jwk":{"type":"object","properties":{"kty":{"type":"string","const":"OKP"},"crv":{"type":"string","const":"Ed25519"},"x":{"type":"string"},"kid":{"type":"string"},"alg":{"type":"string"},"use":{"type":"string","const":"sig"}},"required":["kty","crv","x","kid","alg","use"],"additionalProperties":{}},"Jwks":{"type":"object","properties":{"keys":{"type":"array","items":{"$ref":"#/components/schemas/Jwk"}}},"required":["keys"],"additionalProperties":{}},"Mandate":{"type":"object","properties":{"mandateId":{"type":"string","minLength":1},"issuer":{"type":"object","properties":{"orgId":{"type":"string","minLength":1},"name":{"type":"string","minLength":1}},"required":["orgId","name"]},"agentId":{"type":"string","minLength":1},"validFrom":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"validUntil":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"currency":{"type":"string","const":"EUR"},"perOrderLimitCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"weeklyBudgetCents":{"type":"integer","minimum":0,"maximum":9007199254740991},"allowedCategories":{"minItems":1,"type":"array","items":{"type":"string","minLength":1}},"requiredAttributes":{"type":"array","items":{"type":"string","minLength":1}},"forbiddenAllergens":{"type":"array","items":{"type":"string","minLength":1}},"allergenEvidence":{"type":"string","enum":["declared","declared_or_nature"]},"requiredCertificates":{"type":"array","items":{"type":"string","minLength":1}},"nutritionRules":{"maxItems":30,"type":"array","items":{"type":"object","properties":{"nutrient":{"type":"string","enum":["energy_kcal","fat","saturated_fat","carbohydrate","sugar","fibre","protein","salt","lactose"]},"max":{"type":"number","minimum":0,"maximum":1000},"min":{"type":"number","minimum":0,"maximum":1000},"categories":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}}},"required":["nutrient"]}},"preferredLabels":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"delivery":{"type":"object","properties":{"maxLeadDays":{"type":"integer","exclusiveMinimum":0,"maximum":9007199254740991}},"required":["maxLeadDays"]},"substitution":{"type":"object","properties":{"rule":{"type":"string","enum":["none","same_attributes","within_preferences","needs_approval"]},"maxPriceIncreasePct":{"type":"number","minimum":0}},"required":["rule","maxPriceIncreasePct"]},"approvers":{"type":"array","items":{"type":"object","properties":{"role":{"type":"string","minLength":1},"name":{"type":"string","minLength":1},"maxApprovalCents":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["role","name","maxApprovalCents"]}}},"required":["mandateId","issuer","agentId","validFrom","validUntil","currency","perOrderLimitCents","weeklyBudgetCents","allowedCategories","requiredAttributes","forbiddenAllergens","requiredCertificates","delivery","substitution","approvers"]},"PendingApproval":{"type":"object","properties":{"approvalId":{"type":"string"},"orderId":{"type":"string"},"orgName":{"type":"string"},"agentId":{"type":"string"},"approver":{"type":"object","properties":{"role":{"type":"string"},"name":{"type":"string"},"maxApprovalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["role","name","maxApprovalCents"],"additionalProperties":{}},"totalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reasons":{"type":"array","items":{"type":"string"}},"lines":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"quantity":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"substituteFor":{"type":"string"}},"required":["productId","name","quantity"],"additionalProperties":{}}},"requestedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["approvalId","orderId","orgName","agentId","approver","totalCents","reasons","lines","requestedAt"],"additionalProperties":{}},"StructuredNeed":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":2000},"item":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":200},"category":{"type":"string","minLength":1,"maxLength":60},"attributes":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"excludedAttributes":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"productId":{"type":"string","minLength":1,"maxLength":60}},"required":["text"]},"quantity":{"type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0,"maximum":1000000},"unit":{"type":"string","enum":["L","kg","pcs","pack"]}},"required":["amount","unit"]},"deliverBy":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"deliverOn":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"requiredClaims":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"requiredLabels":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"preferredLabels":{"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"forbiddenAllergens":{"default":[],"maxItems":10,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"optimise":{"default":"unstated","type":"string","enum":["price","co2","reliability","no_preference","unstated"]},"sameAsLastTime":{"default":false,"type":"boolean"}},"required":["item"]},"Concern":{"type":"object","properties":{"key":{"type":"string","description":"Send this key back with the buyer's response."},"code":{"type":"string","description":"What kind of likely mistake, e.g. quantity_tenfold, unit_confusion, delivery_in_past, duplicate_recent."},"severity":{"type":"string","enum":["error","warning"]},"message":{"type":"string","description":"The question to put to the buyer, in plain words."},"confirmable":{"type":"boolean","description":"true: the buyer may confirm the need as it is. false: they must pick a correction."},"corrections":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"patch":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"cancel":{"type":"boolean","const":true}},"required":["id","label"],"additionalProperties":{}}},"preferenceIds":{"type":"array","items":{"type":"string"}},"evidence":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}},"required":["key","code","severity","message","confirmable","corrections"],"additionalProperties":{}},"Question":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string","enum":["dimension","optimise"]},"text":{"type":"string","description":"The question to put to the buyer."},"options":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"}},"required":["id","label"],"additionalProperties":{}},"description":"The only answers AimoPilot accepts: send one optionId."},"dimension":{"type":"string"}},"required":["id","kind","text","options"],"additionalProperties":{}},"OptionCard":{"type":"object","properties":{"optionId":{"type":"string"},"labels":{"type":"array","items":{"type":"string","enum":["best_price","lowest_carbon","most_reliable","same_as_last_time"]}},"title":{"type":"string"},"lines":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"packs":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"packSize":{"type":"number"},"unit":{"type":"string","enum":["L","kg","pcs"]},"unitPriceCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"lineTotalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"deliveryDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"substituteFor":{"type":"string"}},"required":["productId","name","packs","packSize","unit","unitPriceCents","lineTotalCents","deliveryDate"],"additionalProperties":{}}},"totalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"deliveryDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"delivered":{"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["L","kg","pcs"]}},"required":["amount","unit"],"additionalProperties":{}},"leftover":{"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["L","kg","pcs"]}},"required":["amount","unit"],"additionalProperties":{}},"co2":{"type":"object","properties":{"kg":{"type":["number","null"]},"basis":{"type":"string","enum":["product_group_average","missing"]}},"required":["kg","basis"],"additionalProperties":{}},"reliability":{"type":["number","null"]},"signOff":{"type":"array","items":{"type":"string"},"description":"Why a person will have to sign off this option, if they will."},"pros":{"type":"array","items":{"type":"string"}},"cons":{"type":"array","items":{"type":"string"}},"quoteLines":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"quantity":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"substituteFor":{"type":"string"}},"required":["productId","quantity"],"additionalProperties":{}}},"alternative":{"description":"When this card stands in for the product the request is about: how the product knowledge map ranks it for the kitchen use, and why.","type":"object","properties":{"for":{"type":"string"},"tier":{"type":"string","enum":["best","better","good"]},"reason":{"type":"string"},"use":{"type":"string"}},"required":["for","tier","reason"],"additionalProperties":{}}},"required":["optionId","labels","title","lines","totalCents","deliveryDate","delivered","leftover","co2","reliability","signOff","pros","cons","quoteLines"],"additionalProperties":{}},"IntakeStep":{"type":"object","properties":{"requestId":{"type":"string"},"state":{"type":"string","enum":["needs_confirmation","needs_input","options_ready","quoted","no_options","cancelled"],"description":"needs_confirmation, needs_input, options_ready, quoted, no_options or cancelled."},"family":{"type":"string","description":"Product family, e.g. milk."},"need":{"$ref":"#/components/schemas/StructuredNeed"},"amount":{"description":"The need in base units, once AimoPilot could work it out.","type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["L","kg","pcs"]}},"required":["amount","unit"],"additionalProperties":{}},"concerns":{"type":"array","items":{"$ref":"#/components/schemas/Concern"},"description":"needs_confirmation: likely mistakes the buyer must confirm or correct."},"questions":{"type":"array","items":{"$ref":"#/components/schemas/Question"},"description":"needs_input: at most two questions for the buyer."},"fromMemory":{"type":"array","items":{"type":"object","properties":{"question":{"$ref":"#/components/schemas/Question"},"optionId":{"type":"string"},"label":{"type":"string"},"source":{"type":"string","enum":["stated","learned"]},"text":{"type":"string"},"preferenceId":{"type":"string"}},"required":["question","optionId","label","source","text"],"additionalProperties":{}},"description":"Answers AimoPilot took from the kitchen's memory instead of asking."},"memoryUsed":{"description":"One line per remembered item of the kitchen's memory this request used without asking: a rule applied, a habit pre-selected, a check made, a product left out.","type":"array","items":{"type":"object","properties":{"itemId":{"type":"string"},"text":{"type":"string"}},"required":["text"],"additionalProperties":{}}},"alsoAsked":{"description":"Items the buyer's own words name that this request does not cover (found by plain code in the text). Nothing is ordered for them here: submit each as its own need next.","type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"group":{"type":"string"},"amount":{"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string"}},"required":["amount","unit"],"additionalProperties":{}}},"required":["text","group"],"additionalProperties":{}}},"rules":{"type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"source":{"type":"string","enum":["mandate","request","stated"]},"preferenceId":{"type":"string"}},"required":["text","source"],"additionalProperties":{}}},"options":{"type":"array","items":{"$ref":"#/components/schemas/OptionCard"},"description":"options_ready: the choices, with pros and cons."},"preselected":{"type":"object","properties":{"optionId":{"type":"string"},"reason":{"type":"string"},"source":{"type":"string","enum":["request","answer","stated","learned"]}},"required":["optionId","reason","source"],"additionalProperties":{}},"notes":{"type":"array","items":{"type":"string"}},"excluded":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"reason":{"type":"string"}},"required":["productId","reason"],"additionalProperties":{}}},"acknowledgements":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"code":{"type":"string"},"action":{"type":"string","enum":["confirmed","corrected","cancelled"]},"correctionId":{"type":"string"},"by":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["key","code","action","by","at"],"additionalProperties":{}}},"chosen":{"type":"object","properties":{"optionId":{"type":"string"},"labels":{"type":"array","items":{"type":"string"}},"by":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["optionId","labels","by","at"],"additionalProperties":{}},"quote":{"description":"quoted: the signed quote and the gate's preview.","$ref":"#/components/schemas/QuoteResult"},"secondReading":{"description":"The buyer's words read a second time by an independent model (NVIDIA Nemotron) and compared by plain code: agreed, differed (a readings_differ concern asks about each field) or unavailable (read once).","type":"object","properties":{"status":{"type":"string","enum":["agreed","differed","unavailable"]},"firstModel":{"type":"string"},"secondModel":{"type":"string"},"reason":{"type":"string"},"differences":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"first":{"type":"string"},"second":{"type":"string"},"why":{"type":"string"}},"required":["field","first","second","why"],"additionalProperties":{}}}},"required":["status","firstModel","secondModel","differences"],"additionalProperties":{}},"next":{"type":"object","properties":{"method":{"type":"string"},"path":{"type":"string"},"example":{}},"required":["method","path","example"],"additionalProperties":{},"description":"The REST call to make next, with an example body."}},"required":["requestId","state","family","need","concerns","questions","fromMemory","rules","options","notes","excluded","acknowledgements","next"],"additionalProperties":{}},"Kitchen":{"type":"object","properties":{"orgId":{"type":"string"},"name":{"type":"string"},"deliveryWeekdays":{"type":"array","items":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"description":"ISO weekdays the kitchen takes deliveries, 1 = Monday."},"stated":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"kind":{"type":"string"},"text":{"type":"string"},"statedBy":{"type":"string"},"statedAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["id","kind","text","statedBy","statedAt"],"additionalProperties":{}},"description":"Rules people stated for this kitchen."},"families":{"type":"array","items":{"type":"object","properties":{"family":{"type":"string"},"orders":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"usual":{"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string"},"text":{"type":"string"}},"required":["amount","unit","text"],"additionalProperties":{}},"lastOrder":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"learned":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"},"text":{"type":"string"}},"required":["kind","text"],"additionalProperties":{}},"description":"Patterns AimoPilot learned from the history."}},"required":["family","orders","usual","lastOrder","learned"],"additionalProperties":{}}},"recentHistory":{"type":"array","items":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}}}},"required":["orgId","name","deliveryWeekdays","stated","families","recentHistory"],"additionalProperties":{}},"ApprovalView":{"type":"object","properties":{"approvalId":{"type":"string"},"state":{"type":"string","enum":["pending","committed","declined","not_placed"]},"kitchenName":{"type":"string"},"agentName":{"type":"string"},"approverName":{"type":"string"},"approverRoleLabel":{"type":"string"},"items":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string"},"amount":{"type":"string"},"packs":{"type":"string"},"lineTotal":{"type":"string"},"delivery":{"type":"string"},"substituteFor":{"type":"string"}},"required":["name","amount","packs","lineTotal","delivery"],"additionalProperties":{}}},"total":{"type":"string"},"totalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"delivery":{"type":"string"},"why":{"type":"array","items":{"type":"string"},"description":"Why this person is asked, in plain words."},"differences":{"type":"array","items":{"type":"string"},"description":"What differs from what was asked for."},"priceHeldUntil":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"serverNow":{"description":"The server's clock when the view was made; the screen counts down from it, not from the phone's clock.","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"signIn":{"type":"string","enum":["passkey","demo_tap","link_token"],"description":"How this approver signs."},"demoTapEnabled":{"type":"boolean"},"viewerIsApprover":{"type":"boolean"},"outcome":{"type":"object","properties":{"decision":{"type":"string","enum":["approved","declined"]},"signedBy":{"type":"string"},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"method":{"type":"string"},"verified":{"type":"boolean"},"note":{"type":"string"}},"required":["decision","signedBy","at","method","verified"],"additionalProperties":{}},"details":{"type":"object","properties":{"approvalId":{"type":"string"},"orderId":{"type":"string"},"quoteId":{"type":"string"},"orgId":{"type":"string"},"agentId":{"type":"string"},"mandateId":{"type":"string"},"orderDigest":{"type":"string"}},"required":["approvalId","orderId","quoteId","orgId","agentId","mandateId","orderDigest"],"additionalProperties":{}}},"required":["approvalId","state","kitchenName","agentName","approverName","approverRoleLabel","items","total","totalCents","delivery","why","differences","priceHeldUntil","signIn","demoTapEnabled","viewerIsApprover","details"],"additionalProperties":{}},"Sandbox":{"type":"object","properties":{"sandboxId":{"description":"Operations desk and the visitor's own phone only.","type":"string"},"kitchenName":{"type":"string"},"city":{"type":"string"},"approverName":{"type":"string"},"agentName":{"type":"string"},"orgId":{"type":"string"},"agentId":{"type":"string"},"role":{"type":"string"},"roleLabel":{"type":"string"},"passkey":{"description":"false only for the demo-only join without a passkey.","type":"boolean"},"perOrderLimitCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"weeklyBudgetCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"createdAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"expiresAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["kitchenName","city","approverName","createdAt","expiresAt"],"additionalProperties":{}},"DockEvent":{"type":"object","properties":{"id":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Also the SSE event id; send it back as Last-Event-ID to resume."},"type":{"type":"string","enum":["approval_requested","approval_approved","approval_declined","approval_withdrawn","approval_expired","order_committed","order_rejected","order_withdrawn"]},"at":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"orgId":{"description":"Absent on the public aggregated stream.","type":"string"},"orderId":{"description":"Absent on the public aggregated stream.","type":"string"},"approvalId":{"type":"string"},"totalCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"summary":{"type":"string"}},"required":["id","type","at","totalCents","summary"],"additionalProperties":{}},"WebAuthnOptions":{"type":"object","properties":{"challenge":{"type":"string"}},"required":["challenge"],"additionalProperties":{}},"OpenRequestBody":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":2000},"neededBy":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"deliverOn":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"productIds":{"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"category":{"type":"string","minLength":1,"maxLength":60}},"required":["text"]},"AvailabilityBody":{"type":"object","properties":{"requestId":{"type":"string","minLength":1},"productIds":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","minLength":1}},"deliveryDate":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"includeClaims":{"type":"boolean"}},"required":["requestId","productIds"]},"QuoteBody":{"type":"object","properties":{"requestId":{"type":"string","minLength":1},"lines":{"minItems":1,"maxItems":60,"type":"array","items":{"type":"object","properties":{"productId":{"type":"string","minLength":1},"quantity":{"type":"number"},"substituteFor":{"type":"string","minLength":1}},"required":["productId","quantity"]}}},"required":["requestId","lines"]},"PlaceOrderBody":{"type":"object","properties":{"requestId":{"type":"string","minLength":1},"quoteToken":{"description":"The quoteToken from POST /api/v1/quotes, unchanged. Leave it out after the intake's choice: Dock uses the quote it signed for this request.","type":"string","minLength":1},"lines":{"description":"Only with quoteToken: the quote's lines (productId, quantity, unitPriceCents) copied from quote.lines.","type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"quantity":{"type":"number"},"unitPriceCents":{"type":"number"}},"required":["productId","quantity","unitPriceCents"]}},"replaces":{"description":"The orderId this order changes: Dock takes it back once this one is placed, if it still can.","type":"string","minLength":1,"maxLength":60}},"required":["requestId"]},"ApprovalDecisionBody":{"type":"object","properties":{"decision":{"type":"string","enum":["approve","decline"]},"note":{"type":"string","maxLength":500},"proof":{"oneOf":[{"type":"object","properties":{"kind":{"type":"string","const":"webauthn"},"response":{"type":"object","properties":{},"additionalProperties":{},"description":"The passkey assertion (AuthenticationResponseJSON) for the options from POST ./challenge."}},"required":["kind","response"]},{"type":"object","properties":{"kind":{"type":"string","const":"session"}},"required":["kind"],"description":"Decline only, from the phone that joined (approver cookie)."},{"type":"object","properties":{"kind":{"type":"string","const":"demo_tap"}},"required":["kind"],"description":"DEMO ONLY: needs DOCK_DEMO_TAP_APPROVAL=1 and the approver cookie; recorded as unverified."}]},"token":{"description":"Instead of proof: the one-time token from the approval link (approvers without a passkey).","type":"string"}},"required":["decision"]},"ChallengeBody":{"type":"object","properties":{"decision":{"type":"string","enum":["approve","decline"]}},"required":["decision"]},"JoinVerifyBody":{"type":"object","properties":{"response":{"description":"RegistrationResponseJSON from the passkey prompt in the options of POST /api/v1/join.","type":"object","properties":{},"additionalProperties":{}},"fallback":{"description":"DEMO ONLY: true joins without a passkey when DOCK_DEMO_TAP_APPROVAL=1.","type":"boolean"}}},"IntakeBody":{"type":"object","properties":{"need":{"$ref":"#/components/schemas/StructuredNeed"},"by":{"type":"string","minLength":1,"maxLength":120},"site":{"type":"string","minLength":1,"maxLength":80}},"required":["need"]},"InterpretBody":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":2000,"description":"The buyer's request in their own words (English, Finnish or Swedish)."},"now":{"description":"Resolve relative dates against this instant instead of the server clock (tests, replays).","type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$"},"earlier":{"description":"The kitchen's earlier requests, newest first, so a follow-up such as \"same again but 200 litres\" can be resolved.","maxItems":6,"type":"array","items":{"type":"object","properties":{"id":{"type":"string","minLength":1,"maxLength":80,"description":"The earlier request's id (or order id for history without a request)."},"at":{"type":"string","minLength":10,"maxLength":40,"description":"When it was asked or placed (ISO date or timestamp)."},"text":{"anyOf":[{"type":"string","maxLength":2000},{"type":"null"}],"description":"The buyer's own words, when there were any."},"need":{"anyOf":[{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},{"type":"null"}],"description":"The structured need it became, as POST /api/v1/intake took it."},"ordered":{"anyOf":[{"type":"string","maxLength":600},{"type":"null"}],"description":"What was ordered, in one line."},"deliveredOn":{"anyOf":[{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},{"type":"null"}],"description":"Delivery date of what was ordered."}},"required":["id","at","text","need","ordered","deliveredOn"]}}},"required":["text"]},"Interpretation":{"type":"object","properties":{"need":{"$ref":"#/components/schemas/StructuredNeed"},"model":{"type":"string","description":"provider:model-id that interpreted the text."},"ms":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Time the model took, in milliseconds."},"cached":{"description":"True when the same text for the same kitchen was already interpreted today: no model call.","type":"boolean"},"alsoAsked":{"description":"Other items the buyer's text names that this need does not cover, found by plain code in the text (never the model's word). Interpret or submit each next; the intake step repeats them.","type":"array","items":{"type":"object","properties":{"text":{"type":"string"},"group":{"type":"string"},"amount":{"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string"}},"required":["amount","unit"]}},"required":["text","group"]}},"basedOn":{"description":"Present when the text referred to one of the earlier requests: which one, and the fields taken from it.","type":"object","properties":{"requestId":{"type":"string"},"fields":{"type":"array","items":{"type":"string","enum":["item","quantity","deliverBy","attributes","optimise","allergens"]}}},"required":["requestId","fields"]},"secondReading":{"description":"The words read a second time by an independent open-weights model (NVIDIA Nemotron), compared by plain code. It never changes the need: when the readings differ on something material, POST /api/v1/intake with this need asks the buyer about exactly that field first.","type":"object","properties":{"status":{"type":"string","enum":["agreed","differed","unavailable"]},"firstModel":{"type":"string"},"secondModel":{"type":"string"},"reason":{"description":"unavailable: timeout, at_capacity, failed, unreadable, over_limit, not_a_purchase or not_configured.","type":"string"},"differences":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string","enum":["amount","claims","allergens","kind","date"]},"first":{"type":"string"},"second":{"type":"string"},"why":{"type":"string"}},"required":["field","first","second","why"]}}},"required":["status","firstModel","secondModel","differences"]}},"required":["need","model","ms"]},"ConcernResponseBody":{"type":"object","properties":{"by":{"type":"string","minLength":1,"maxLength":120},"responses":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"object","properties":{"key":{"type":"string","minLength":1},"choice":{"type":"string","minLength":1}},"required":["key","choice"]}}},"required":["by","responses"]},"AnswersBody":{"type":"object","properties":{"by":{"type":"string","minLength":1,"maxLength":120},"answers":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"object","properties":{"questionId":{"type":"string","minLength":1},"optionId":{"type":"string","minLength":1},"remember":{"type":"boolean"}},"required":["questionId","optionId"]}}},"required":["by","answers"]},"ChoiceBody":{"type":"object","properties":{"by":{"type":"string","minLength":1,"maxLength":120},"optionId":{"type":"string","minLength":1}},"required":["by","optionId"]},"RequestCreated":{"type":"object","properties":{"request":{"$ref":"#/components/schemas/PurchaseRequest"}},"required":["request"]},"ProductList":{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"liveCallsSoFar":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["products","liveCallsSoFar"]},"SignedClaim":{"type":"object","properties":{"claimId":{"type":"string"},"type":{"type":"string","enum":["price","availability","origin"]},"productId":{"type":"string"},"sha256":{"type":"string","description":"Hex SHA-256 of the token; a quote's evidence names claims by id and this hash."},"token":{"type":"string","description":"W3C Verifiable Credential secured as a compact JWS (typ vc+jwt, Ed25519), verifiable offline against /api/v1/keys."},"validUntil":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["claimId","type","productId","sha256","token","validUntil"],"additionalProperties":{}},"OfferList":{"type":"object","properties":{"offers":{"type":"array","items":{"$ref":"#/components/schemas/SignedOffer"}},"prefs":{"$ref":"#/components/schemas/OfferPrefs"},"keysUrl":{"type":"string"}},"required":["offers","prefs","keysUrl"],"additionalProperties":{}},"OrderEnvelope":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"nextStep":{"type":"string","description":"What to do next, in one or two sentences: the same guidance an MCP tool result carries."},"receiptEmail":{"description":"On a committed order: whether its receipt was emailed to the kitchen (sent after the fact: `sending` right after the order, `sent` once it went). Absent otherwise.","type":"object","properties":{"emailed":{"type":"boolean"},"to":{"description":"The kitchen's address, masked: m•••@example.com.","type":"string"},"status":{"type":"string","enum":["sent","sending","failed","skipped","no_address","not_confirmed","not_configured","not_committed"]}},"required":["emailed","status"]}},"required":["order"]},"PlacedEnvelope":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"nextStep":{"type":"string","description":"What to do next, in one or two sentences: the same guidance an MCP tool result carries."},"receiptEmail":{"description":"On a committed order: whether its receipt was emailed to the kitchen (sent after the fact: `sending` right after the order, `sent` once it went). Absent otherwise.","type":"object","properties":{"emailed":{"type":"boolean"},"to":{"description":"The kitchen's address, masked: m•••@example.com.","type":"string"},"status":{"type":"string","enum":["sent","sending","failed","skipped","no_address","not_confirmed","not_configured","not_committed"]}},"required":["emailed","status"]},"replaced":{"description":"With replaces: the earlier order's new status, or why it still stands.","type":"object","properties":{"orderId":{"type":"string"},"status":{"type":"string"},"refused":{"type":"object","properties":{"code":{"type":"string"},"rule":{"type":"string"},"message":{"type":"string"}},"required":["code","message"]}},"required":["orderId","status"]}},"required":["order"]},"WaitedEnvelope":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"nextStep":{"type":"string","description":"What to do next, in one or two sentences: the same guidance an MCP tool result carries."},"receiptEmail":{"description":"On a committed order: whether its receipt was emailed to the kitchen (sent after the fact: `sending` right after the order, `sent` once it went). Absent otherwise.","type":"object","properties":{"emailed":{"type":"boolean"},"to":{"description":"The kitchen's address, masked: m•••@example.com.","type":"string"},"status":{"type":"string","enum":["sent","sending","failed","skipped","no_address","not_confirmed","not_configured","not_committed"]}},"required":["emailed","status"]},"decided":{"type":"boolean"},"waitedSeconds":{"type":"number"}},"required":["order"]},"LookupResult":{"type":"object","properties":{"requestId":{"type":"string"},"matched":{"type":"number"},"products":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"brand":{"type":"string"},"category":{"type":"string"},"match":{"description":"From the product search: the kind asked for, a variant of it, or something that could stand in.","type":"string","enum":["exact","variant","stand_in","not_the_kind"]},"pack":{"type":"object","properties":{"size":{"type":"number"},"unit":{"type":"string"}},"required":["size","unit"],"additionalProperties":{}},"claims":{"type":"array","items":{"type":"string"},"description":"Dietary and other claims, e.g. lactose_free."},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and labels."},"allergens":{"type":"object","properties":{"declared":{"type":"boolean"},"contains":{"type":"array","items":{"type":"string"}},"mayContain":{"type":"array","items":{"type":"string"}},"freeFrom":{"type":"array","items":{"type":"string"}}},"required":["declared","contains","mayContain","freeFrom"]},"origin":{"type":["string","null"]},"deliveryReliability":{"description":"Share of past deliveries on time; null when unknown.","type":["number","null"]},"carbon":{"type":"object","properties":{"basis":{"type":"string","enum":["product_group_average","missing"]},"kgCo2ePerUnit":{"type":["number","null"]},"groupAverageKgCo2ePerKg":{"type":["number","null"]}},"required":["basis","kgCo2ePerUnit","groupAverageKgCo2ePerKg"],"additionalProperties":{}},"live":{"type":"object","properties":{"unitPriceCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live price per pack, euro cents ex VAT."},"perUnit":{"type":"object","properties":{"cents":{"type":"number"},"unit":{"type":"string"}},"required":["cents","unit"]},"availablePacks":{"description":"null: the wholesaler reports no real stock figure (supplier-shipped).","type":["number","null"]},"stock":{"type":"string","enum":["reported","not_reported"]},"earliestDelivery":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"orderBy":{"description":"Order cut-off for that delivery, as the wholesaler states it.","type":"string"},"meetsDay":{"description":"With deliverOn: can arrive that day.","type":"boolean"},"packsForQuantity":{"type":"number"},"coversQuantity":{"type":["boolean","null"]}},"required":["unitPriceCents","perUnit","availablePacks","stock","earliestDelivery"],"additionalProperties":{}},"unavailable":{"type":"object","properties":{"reason":{"type":"string"}},"required":["reason"],"additionalProperties":{}}},"required":["productId","name","brand","category","pack","claims","labels","allergens","origin","deliveryReliability","carbon"],"additionalProperties":{}}},"outsideMandate":{"type":"object","properties":{"count":{"type":"number"}},"required":["count"],"additionalProperties":{}},"liveChecked":{"type":"number"},"search":{"type":"object","properties":{"path":{"type":"string"},"notSold":{"type":"boolean"},"onlyStandins":{"type":"boolean"}},"required":["path","notSold","onlyStandins"],"additionalProperties":{}}},"required":["requestId","matched","products","outsideMandate","liveChecked"],"additionalProperties":{}},"AlternativesResult":{"type":"object","properties":{"requestId":{"type":"string"},"requested":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"brand":{"type":"string"},"category":{"type":"string"},"match":{"description":"From the product search: the kind asked for, a variant of it, or something that could stand in.","type":"string","enum":["exact","variant","stand_in","not_the_kind"]},"pack":{"type":"object","properties":{"size":{"type":"number"},"unit":{"type":"string"}},"required":["size","unit"],"additionalProperties":{}},"claims":{"type":"array","items":{"type":"string"},"description":"Dietary and other claims, e.g. lactose_free."},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and labels."},"allergens":{"type":"object","properties":{"declared":{"type":"boolean"},"contains":{"type":"array","items":{"type":"string"}},"mayContain":{"type":"array","items":{"type":"string"}},"freeFrom":{"type":"array","items":{"type":"string"}}},"required":["declared","contains","mayContain","freeFrom"]},"origin":{"type":["string","null"]},"deliveryReliability":{"description":"Share of past deliveries on time; null when unknown.","type":["number","null"]},"carbon":{"type":"object","properties":{"basis":{"type":"string","enum":["product_group_average","missing"]},"kgCo2ePerUnit":{"type":["number","null"]},"groupAverageKgCo2ePerKg":{"type":["number","null"]}},"required":["basis","kgCo2ePerUnit","groupAverageKgCo2ePerKg"],"additionalProperties":{}},"live":{"type":"object","properties":{"unitPriceCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live price per pack, euro cents ex VAT."},"perUnit":{"type":"object","properties":{"cents":{"type":"number"},"unit":{"type":"string"}},"required":["cents","unit"]},"availablePacks":{"description":"null: the wholesaler reports no real stock figure (supplier-shipped).","type":["number","null"]},"stock":{"type":"string","enum":["reported","not_reported"]},"earliestDelivery":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"orderBy":{"description":"Order cut-off for that delivery, as the wholesaler states it.","type":"string"},"meetsDay":{"description":"With deliverOn: can arrive that day.","type":"boolean"},"packsForQuantity":{"type":"number"},"coversQuantity":{"type":["boolean","null"]}},"required":["unitPriceCents","perUnit","availablePacks","stock","earliestDelivery"],"additionalProperties":{}},"unavailable":{"type":"object","properties":{"reason":{"type":"string"}},"required":["reason"],"additionalProperties":{}}},"required":["productId","name","brand","category","pack","claims","labels","allergens","origin","deliveryReliability","carbon"],"additionalProperties":{}},"rankedBy":{"type":"string"},"alternatives":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"brand":{"type":"string"},"category":{"type":"string"},"match":{"description":"From the product search: the kind asked for, a variant of it, or something that could stand in.","type":"string","enum":["exact","variant","stand_in","not_the_kind"]},"pack":{"type":"object","properties":{"size":{"type":"number"},"unit":{"type":"string"}},"required":["size","unit"],"additionalProperties":{}},"claims":{"type":"array","items":{"type":"string"},"description":"Dietary and other claims, e.g. lactose_free."},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and labels."},"allergens":{"type":"object","properties":{"declared":{"type":"boolean"},"contains":{"type":"array","items":{"type":"string"}},"mayContain":{"type":"array","items":{"type":"string"}},"freeFrom":{"type":"array","items":{"type":"string"}}},"required":["declared","contains","mayContain","freeFrom"]},"origin":{"type":["string","null"]},"deliveryReliability":{"description":"Share of past deliveries on time; null when unknown.","type":["number","null"]},"carbon":{"type":"object","properties":{"basis":{"type":"string","enum":["product_group_average","missing"]},"kgCo2ePerUnit":{"type":["number","null"]},"groupAverageKgCo2ePerKg":{"type":["number","null"]}},"required":["basis","kgCo2ePerUnit","groupAverageKgCo2ePerKg"],"additionalProperties":{}},"live":{"type":"object","properties":{"unitPriceCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Live price per pack, euro cents ex VAT."},"perUnit":{"type":"object","properties":{"cents":{"type":"number"},"unit":{"type":"string"}},"required":["cents","unit"]},"availablePacks":{"description":"null: the wholesaler reports no real stock figure (supplier-shipped).","type":["number","null"]},"stock":{"type":"string","enum":["reported","not_reported"]},"earliestDelivery":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"orderBy":{"description":"Order cut-off for that delivery, as the wholesaler states it.","type":"string"},"meetsDay":{"description":"With deliverOn: can arrive that day.","type":"boolean"},"packsForQuantity":{"type":"number"},"coversQuantity":{"type":["boolean","null"]}},"required":["unitPriceCents","perUnit","availablePacks","stock","earliestDelivery"],"additionalProperties":{}},"unavailable":{"type":"object","properties":{"reason":{"type":"string"}},"required":["reason"],"additionalProperties":{}},"rank":{"type":"string","enum":["best","better","good"]},"packs":{"type":"number"},"totalCents":{"type":"number"},"tradeOffs":{"type":"array","items":{"type":"string"}},"gate":{"type":"object","properties":{"outcome":{"type":"string"},"reasons":{"type":"array","items":{"type":"string"}}},"required":["outcome","reasons"],"additionalProperties":{}}},"required":["productId","name","brand","category","pack","claims","labels","allergens","origin","deliveryReliability","carbon","rank","packs","totalCents","tradeOffs","gate"],"additionalProperties":{}}},"leftOut":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"reason":{"type":"string"}},"required":["productId","reason"],"additionalProperties":{}}},"none":{"type":"string"}},"required":["requestId","requested","rankedBy","alternatives","leftOut"],"additionalProperties":{}},"OrderList":{"type":"object","properties":{"orders":{"type":"array","items":{"type":"object","properties":{"orderId":{"type":"string"},"requestId":{"type":"string"},"status":{"type":"string"},"totalCents":{"type":"number"}},"required":["orderId","requestId","status","totalCents"],"additionalProperties":{}}},"waitingForSignOff":{"type":"number"}},"required":["orders","waitingForSignOff"]},"OrderRefused":{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"},"order":{"$ref":"#/components/schemas/Order"},"nextStep":{"type":"string","description":"What to do next, in one or two sentences: the same guidance an MCP tool result carries."}},"required":["error","order"]},"Refusal":{"anyOf":[{"$ref":"#/components/schemas/OrderRefused"},{"$ref":"#/components/schemas/ErrorBody"}]},"ApprovalList":{"type":"object","properties":{"approvals":{"type":"array","items":{"$ref":"#/components/schemas/PendingApproval"}}},"required":["approvals"]},"LedgerView":{"type":"object","properties":{"verification":{"$ref":"#/components/schemas/LedgerVerification"},"head":{"anyOf":[{"type":"object","properties":{"seq":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"hash":{"type":"string"}},"required":["seq","hash"]},{"type":"null"}]},"catalog":{"$ref":"#/components/schemas/CatalogStats"},"view":{"type":"string","enum":["buyer","wholesaler"]},"explanation":{"type":"array","items":{"type":"string"}},"entries":{"type":"array","items":{"anyOf":[{"$ref":"#/components/schemas/LedgerEntry"},{"type":"object","properties":{"seq":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"at":{"type":"string"},"hash":{"type":"string"},"type":{"type":"string"},"kind":{"type":"string"},"summary":{"type":"string"}},"required":["seq","at","hash","type","kind","summary"],"additionalProperties":{}}]}}},"required":["verification","head","catalog","explanation","entries"]},"QuoteCreated":{"type":"object","properties":{"quote":{"$ref":"#/components/schemas/Quote"},"quoteToken":{"type":"string","description":"The quote as a compact JWS signed by the wholesaler (typ quote+jws). Pass it unchanged to place an order."},"policyPreview":{"description":"What the gate would decide on this quote right now. Not binding.","$ref":"#/components/schemas/Decision"},"nextStep":{"type":"string","description":"What to do next, in one or two sentences: the same guidance an MCP tool result carries."}},"required":["quote","quoteToken","policyPreview"],"additionalProperties":{}},"IntakeStepEnvelope":{"type":"object","properties":{"step":{"$ref":"#/components/schemas/IntakeStep"},"waitingFor":{"description":"buyer: relay the step to the human buyer and wait for them. agent: your move. nobody: finished.","type":"string","enum":["buyer","agent","nobody"]},"nextStep":{"type":"string","description":"What to do next, in one or two sentences: the same guidance an MCP tool result carries."},"answerUrl":{"description":"On a step that waits for the buyer (needs_confirmation, needs_input): a one-time page on Dock where the buyer answers Dock's fixed choices themselves. Give it to the buyer instead of relaying; it works once and expires in 30 minutes.","type":"string"}},"required":["step"]},"KitchenEnvelope":{"type":"object","properties":{"kitchen":{"$ref":"#/components/schemas/Kitchen"}},"required":["kitchen"]},"ApprovalViewEnvelope":{"type":"object","properties":{"view":{"$ref":"#/components/schemas/ApprovalView"}},"required":["view"]},"ApprovalDecided":{"type":"object","properties":{"order":{"$ref":"#/components/schemas/Order"},"view":{"$ref":"#/components/schemas/ApprovalView"}},"required":["order","view"]},"ApprovalRefused":{"anyOf":[{"type":"object","properties":{"error":{"$ref":"#/components/schemas/Error"},"order":{"$ref":"#/components/schemas/Order"},"view":{"$ref":"#/components/schemas/ApprovalView"}},"required":["error","order","view"]},{"$ref":"#/components/schemas/ErrorBody"}]},"ChallengeOptions":{"type":"object","properties":{"options":{"$ref":"#/components/schemas/WebAuthnOptions"}},"required":["options"]},"ApprovalLink":{"type":"object","properties":{"approvalId":{"type":"string"},"orderId":{"type":"string"},"status":{"type":"string","enum":["pending","approved","declined"]},"url":{"type":"string","description":"The sign-off screen. No secret in it: approving needs the approver's passkey."},"qrPayload":{"type":"string"},"qrSvg":{"type":"string"}},"required":["approvalId","orderId","status","url","qrPayload","qrSvg"]},"ApproverHome":{"type":"object","properties":{"approver":{"type":"object","properties":{"name":{"type":"string"},"roleLabel":{"type":"string"},"passkey":{"type":"boolean"}},"required":["name","roleLabel","passkey"]},"kitchen":{"type":"object","properties":{"name":{"type":"string"},"city":{"type":"string"}},"required":["name","city"]},"agent":{"type":"object","properties":{"name":{"type":"string"},"perOrderLimit":{"type":"string","description":"Whole euros as \"€500\", otherwise \"€499.50\"."},"weeklyBudget":{"type":"string","description":"Whole euros as \"€500\", otherwise \"€499.50\"."}},"required":["name","perOrderLimit","weeklyBudget"]},"expiresAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"},"expiresAtText":{"type":"string"},"demoTapEnabled":{"type":"boolean"},"pending":{"type":"array","items":{"type":"object","properties":{"approvalId":{"type":"string"},"total":{"type":"string","description":"Whole euros as \"€500\", otherwise \"€499.50\"."},"requestedAtText":{"type":"string"}},"required":["approvalId","total","requestedAtText"]}},"recent":{"type":"array","items":{"type":"object","properties":{"approvalId":{"type":"string"},"status":{"type":"string"},"total":{"type":"string","description":"Whole euros as \"€500\", otherwise \"€499.50\"."},"delivery":{"type":"string"},"atText":{"type":"string"}},"required":["status","total","atText"]}}},"required":["approver","kitchen","agent","expiresAt","expiresAtText","demoTapEnabled","pending","recent"]},"JoinStart":{"type":"object","properties":{"options":{"description":"PublicKeyCredentialCreationOptionsJSON for startRegistration().","$ref":"#/components/schemas/WebAuthnOptions"},"identity":{"type":"object","properties":{"kitchenName":{"type":"string"},"city":{"type":"string"},"approverName":{"type":"string"},"agentName":{"type":"string"}},"required":["kitchenName","city","approverName","agentName"]},"perOrderLimit":{"type":"string","description":"Whole euros as \"€500\", otherwise \"€499.50\"."},"current":{"description":"The kitchen this phone already joined, if any.","type":"object","properties":{"kitchenName":{"type":"string"},"approverName":{"type":"string"},"expiresAt":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["kitchenName","approverName","expiresAt"]},"secureOrigin":{"type":"boolean","description":"Passkeys need https or localhost."},"demoTapEnabled":{"type":"boolean"}},"required":["options","identity","perOrderLimit","secureOrigin","demoTapEnabled"]},"Joined":{"type":"object","properties":{"sandbox":{"$ref":"#/components/schemas/Sandbox"},"next":{"type":"string"}},"required":["sandbox","next"]},"SandboxList":{"type":"object","properties":{"sandboxes":{"type":"array","items":{"$ref":"#/components/schemas/Sandbox"}}},"required":["sandboxes"]},"DemoOrderStarted":{"type":"object","properties":{"order":{"type":"object","properties":{"orderId":{"type":"string"},"status":{"type":"string","enum":["pending_approval","committed","rejected","declined"]},"approvalId":{"type":"string"}},"required":["orderId","status"]}},"required":["order"]},"SignedOffer":{"type":"object","properties":{"offerId":{"type":"string"},"status":{"type":"string","enum":["offered","accepted","declined","muted","expired","withheld"]},"verified":{"type":"boolean","description":"Verified against the wholesaler key and addressed to this organisation."},"token":{"type":"string","description":"W3C Verifiable Credential (type DockProductOffer) as a compact JWS (typ vc+jwt, Ed25519), verifiable offline against /.well-known/jwks.json."},"offer":{"type":"object","properties":{"offerId":{"type":"string"},"isOrder":{"type":"boolean","const":false},"product":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"category":{"type":"string"}},"required":["productId","name","category"],"additionalProperties":{}},"reasons":{"type":"array","items":{"type":"object","properties":{"kind":{"type":"string"}},"required":["kind"],"additionalProperties":{}}},"line":{"type":"string"},"price":{"type":"object","properties":{"unitPriceCents":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"currency":{"type":"string","const":"EUR"}},"required":["unitPriceCents","currency"],"additionalProperties":{}},"suggestedPacks":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"validUntil":{"type":"string","format":"date-time","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z))$"}},"required":["offerId","isOrder","product","reasons","line","price","suggestedPacks","validUntil"],"additionalProperties":{}}},"required":["offerId","status","verified","token","offer"],"additionalProperties":{}},"OfferPrefs":{"type":"object","properties":{"off":{"type":"object","properties":{"by":{"type":"string"},"at":{"type":"string"}},"required":["by","at"],"additionalProperties":{}},"mutes":{"type":"array","items":{"type":"object","properties":{"scope":{"type":"string"},"value":{"type":"string"}},"required":["scope","value"],"additionalProperties":{}}}},"required":["mutes"],"additionalProperties":{}},"OfferAcceptBody":{"type":"object","properties":{"packs":{"type":"integer","exclusiveMinimum":0,"maximum":10000},"deliverBy":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"by":{"type":"string","minLength":1,"maxLength":120}}},"OfferAnswerBody":{"type":"object","properties":{"by":{"type":"string","minLength":1,"maxLength":120}}},"OfferMuteBody":{"type":"object","properties":{"scope":{"type":"string","enum":["product","family","reason","all"]},"offerId":{"type":"string"},"value":{"type":"string","minLength":1,"maxLength":60},"undo":{"type":"boolean"},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["scope"]},"Watch":{"type":"object","properties":{"findings":{"type":"array","items":{"type":"object","properties":{"findingId":{"type":"string"},"state":{"type":"string"},"substitutes":{"type":"array","items":{"type":"object","properties":{"rank":{"type":"string"},"productId":{"type":"string"},"insidePreferences":{"type":"boolean"}},"required":["rank","productId","insidePreferences"],"additionalProperties":{}}}},"required":["findingId","state","substitutes"],"additionalProperties":{}}},"renewals":{"type":"array","items":{"type":"object","properties":{"renewalId":{"type":"string"},"state":{"type":"string"}},"required":["renewalId","state"],"additionalProperties":{}}},"standing":{"type":"array","items":{"type":"object","properties":{"family":{"type":"string"}},"required":["family"],"additionalProperties":{}}},"staged":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}},"keysUrl":{"type":"string"}},"required":["findings","renewals","standing","staged","keysUrl"],"additionalProperties":{}},"WatchAnswerBody":{"type":"object","properties":{"findingId":{"type":"string"},"renewalId":{"type":"string"},"choice":{"type":"string","enum":["switch","keep","renew_new","usual","skip"]},"productId":{"type":"string"},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["choice","by"]},"WatchAnswered":{"type":"object","properties":{"state":{"type":"string"},"order":{"type":"object","properties":{},"additionalProperties":{}},"refused":{"type":"object","properties":{},"additionalProperties":{}}},"required":["state"],"additionalProperties":{}},"AutoRenewBody":{"type":"object","properties":{"family":{"type":"string","minLength":1,"maxLength":60},"on":{"type":"boolean"},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["family","on","by"]},"BackupBody":{"type":"object","properties":{"orderId":{"type":"string"},"productId":{"type":"string"},"choice":{"type":"string","maxLength":60},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["orderId"]},"BackupResult":{"type":"object","properties":{"backups":{"type":"array","items":{"type":"object","properties":{"backupId":{"type":"string"},"record":{"type":"object","properties":{"share":{"type":"number"},"words":{"type":"string"}},"required":["share","words"],"additionalProperties":{}}},"required":["backupId","record"],"additionalProperties":{}}},"backup":{"type":"object","properties":{"backupId":{"type":"string"},"state":{"type":"string"}},"required":["backupId","state"],"additionalProperties":{}}},"additionalProperties":{}},"AutoRenew":{"type":"object","properties":{"autoRenew":{"type":"object","properties":{"family":{"type":"string"},"on":{"type":"boolean"},"by":{"type":"string"}},"required":["family","on","by"],"additionalProperties":{}}},"required":["autoRenew"],"additionalProperties":{}},"OfferAccepted":{"type":"object","properties":{"offerId":{"type":"string"}},"required":["offerId"],"additionalProperties":{}},"OfferDeclined":{"type":"object","properties":{"offer":{"type":"object","properties":{"offerId":{"type":"string"},"status":{"type":"string"}},"required":["offerId","status"],"additionalProperties":{}}},"required":["offer"],"additionalProperties":{}},"OfferPrefsReply":{"type":"object","properties":{"prefs":{"$ref":"#/components/schemas/OfferPrefs"}},"required":["prefs"],"additionalProperties":{}},"MenuSeasonalBody":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":2000,"description":"The chef's words."},"covers":{"type":"integer","exclusiveMinimum":0,"maximum":20000},"deliverBy":{"description":"Service day, YYYY-MM-DD.","type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"have":{"description":"What the kitchen says it has.","maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["text"]},"MenuEventBody":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":2000,"description":"The booking, in the buyer's words."},"deliverBy":{"description":"YYYY-MM-DD; default: the day the words name.","type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"full":{"description":"Also plan a replacement dish where nothing replaces an ingredient (slower, over 60 s).","type":"boolean"},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["text"]},"MenuOrderBody":{"type":"object","properties":{"choices":{"description":"The buyer's pick per ingredient that cannot arrive. Left out: the best option, only where no sign-off is needed.","maxItems":20,"type":"array","items":{"type":"object","properties":{"lineId":{"type":"string","minLength":1},"optionId":{"type":"string","minLength":1}},"required":["lineId","optionId"]}},"acceptPartial":{"description":"Only on the buyer's own words: order what can be bought when the plan is not the whole dish.","type":"boolean"},"by":{"type":"string","minLength":1,"maxLength":120}}},"MenuPlan":{"type":"object","properties":{"result":{"type":"object","properties":{"scenario":{"type":"string","enum":["seasonal","event"]},"planId":{"type":"string"},"requestId":{"type":["string","null"]},"lines":{"type":"array","items":{"type":"object","properties":{"lineId":{"type":"string"},"ingredient":{"type":"string"}},"required":["lineId","ingredient"],"additionalProperties":{}}}},"required":["scenario","planId","requestId","lines"],"additionalProperties":{}},"nextStep":{"type":"string"}},"required":["result","nextStep"],"additionalProperties":{}},"MenuIdeasBody":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":2000,"description":"What the buyer is looking for: occasion, style, diet, budget, how many, the day."},"have":{"description":"What the kitchen has, in words.","type":"string","maxLength":500},"by":{"type":"string","minLength":1,"maxLength":120}},"required":["text"]},"MenuDishBody":{"type":"object","properties":{"ideasId":{"type":"string","minLength":1},"dishId":{"type":"string","minLength":1},"planId":{"description":"A plan already made: its three groups again.","type":"string","minLength":1},"covers":{"type":"integer","exclusiveMinimum":0,"maximum":20000},"deliverBy":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"by":{"type":"string","minLength":1,"maxLength":120}}},"MenuIdeas":{"type":"object","properties":{"result":{"type":"object","properties":{"ideasId":{"type":"string"},"ideas":{"type":"array","items":{"type":"object","properties":{"dishId":{"type":"string"},"name":{"type":"string"},"buyable":{"type":"boolean"}},"required":["dishId","name","buyable"],"additionalProperties":{}}}},"required":["ideasId","ideas"],"additionalProperties":{}},"nextStep":{"type":"string"}},"required":["result","nextStep"],"additionalProperties":{}},"MenuDish":{"type":"object","properties":{"result":{"type":"object","properties":{"planId":{"type":"string"},"complete":{"type":"boolean"},"onHand":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}},"toOrder":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}},"cannotGet":{"type":"array","items":{"type":"object","properties":{},"additionalProperties":{}}}},"required":["planId","complete","onHand","toOrder","cannotGet"],"additionalProperties":{}},"nextStep":{"type":"string"}},"required":["result","nextStep"],"additionalProperties":{}},"MenuOrdered":{"type":"object","properties":{"sheet":{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"packs":{"type":"number"}},"required":["productId","packs"],"additionalProperties":{}}}},"required":["lines"],"additionalProperties":{}},"order":{"$ref":"#/components/schemas/Order"},"nextStep":{"type":"string"}},"required":["sheet","order","nextStep"],"additionalProperties":{}},"ApiIndex":{"type":"object","properties":{"name":{"type":"string"},"version":{"type":"string"},"endpoints":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{"type":"string"}}},"required":["name","version","endpoints"]},"OpenApiDocument":{"type":"object","properties":{"openapi":{"type":"string"}},"required":["openapi"]},"LookupBody":{"type":"object","properties":{"requestId":{"description":"Your request, if you have one.","type":"string","minLength":1},"text":{"description":"e.g. 'lactose-free milk'.","type":"string","minLength":1,"maxLength":120},"category":{"description":"e.g. milk, cream, plant_drink.","type":"string","minLength":1,"maxLength":60},"brand":{"type":"string","minLength":1,"maxLength":60},"productIds":{"minItems":1,"maxItems":20,"type":"array","items":{"type":"string","minLength":1,"maxLength":60}},"requiredClaims":{"description":"e.g. ['lactose_free'].","maxItems":8,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"forbiddenAllergens":{"description":"Unknown allergen data counts as unsafe.","maxItems":14,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"packSize":{"description":"Pack size window.","type":"object","properties":{"min":{"type":"number","exclusiveMinimum":0},"max":{"type":"number","exclusiveMinimum":0},"unit":{"type":"string","enum":["L","kg","pcs"]}}},"quantity":{"description":"Packs needed and whether stock covers it.","type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0,"maximum":1000000},"unit":{"type":"string","enum":["L","kg","pcs"]}},"required":["amount","unit"]},"deliverOn":{"description":"Wanted delivery day.","type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"live":{"description":"false: master data only (free).","type":"boolean"},"limit":{"description":"Default 5.","type":"integer","minimum":1,"maximum":10}}},"AlternativesBody":{"type":"object","properties":{"requestId":{"description":"Your request, if you have one.","type":"string","minLength":1},"productId":{"type":"string","minLength":1,"maxLength":60},"quantity":{"type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0,"maximum":1000000},"unit":{"type":"string","enum":["L","kg","pcs"]}},"required":["amount","unit"]},"deliverOn":{"type":"string","format":"date","pattern":"^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))$"},"requiredClaims":{"maxItems":8,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"forbiddenAllergens":{"maxItems":14,"type":"array","items":{"type":"string","minLength":1,"maxLength":40}},"rankBy":{"description":"Default same_kind.","type":"string","enum":["same_kind","price","co2","reliability","pack_size"]}},"required":["productId"]}}}}