{"openapi":"3.1.0","jsonSchemaDialect":"https://json-schema.org/draft/2020-12/schema","info":{"title":"Valio Aimo agent ordering (Laituri)","version":"0.1.0","summary":"Search, quote and order from the wholesaler through signed offers and a buyer mandate.","description":"Flow: search products, request a signed quote, place the order with the offer and mandate JWS. Every step is written to a signed, hash-chained audit log. MCP is available at /mcp."},"servers":[{"url":"https://laituri.wicflow.com"}],"tags":[{"name":"Catalogue"},{"name":"Ordering"},{"name":"Approvals"},{"name":"Mandate"},{"name":"Audit"},{"name":"Supplier"},{"name":"System"}],"paths":{"/api/v1/products/search":{"post":{"operationId":"searchCatalog","summary":"Find products for a need in plain language","description":"Returns ranked products with net content per sales unit, claims, allergens and an interpretation with a match confidence. Every hit carries facts the wholesaler vouches for: country of origin, labels, delivery reliability and the category-average CO2 per kg. With a clear match, prefer (price, reliability or sustainability) sets the ranking; price is the default.","tags":["Catalogue"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchRequest"},"example":{"query":"laktoositon maito 1 l","filters":{"claims":["lactose_free"],"categoryGroup":"dairy"},"limit":5,"need":{"amount":1000,"unit":"l"},"deliverBy":"2026-11-16"}}}},"responses":{"200":{"description":"Find products for a need in plain language","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SearchResponse"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":["catalog:read"]},{}],"x-audience":"agent"}},"/api/v1/products/{id}":{"get":{"operationId":"getProduct","summary":"Read one product","tags":["Catalogue"],"parameters":[{"name":"id","in":"path","required":true,"description":"Wholesaler product code, for example 464306","schema":{"type":"string"}}],"responses":{"200":{"description":"Read one product","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":["catalog:read"]},{}],"x-audience":"agent"}},"/api/v1/quotes":{"post":{"operationId":"requestQuote","summary":"Get a signed fixed-price offer","description":"Prices the request with live price and availability. Returns a wholesaler-signed offer valid for 15 minutes and signed alternatives when the request cannot be met in full and on time. Each offer line carries the signed product facts (origin, labels, delivery reliability, category-average CO2). Each alternative has a tier (good, better, best) and the one-sentence meaning of that tier.","tags":["Ordering"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteRequest"},"example":{"lines":[{"productId":"464306","quantity":{"amount":1000,"unit":"l"}}],"deliverBy":"2026-11-16","intent":"1,000 litres of lactose-free milk by Monday","matchConfidence":"high"}}}},"responses":{"200":{"description":"Get a signed fixed-price offer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteResponse"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The wholesaler API did not answer","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":["quote:create"]},{}],"x-audience":"agent"}},"/api/v1/offers/{id}":{"get":{"operationId":"getOffer","summary":"Read an issued offer","description":"Needs an agent key (its own buyer's offers only), the browser's sandbox or a signed-in session; 401 without any.","tags":["Ordering"],"parameters":[{"name":"id","in":"path","required":true,"description":"Offer id","schema":{"type":"string"}}],"responses":{"200":{"description":"Read an issued offer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedOffer"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":["quote:create"]},{}],"x-audience":"agent"}},"/api/v1/orders":{"post":{"operationId":"placeOrder","summary":"Place an order from a signed offer","description":"Checks the offer and mandate signatures and every mandate rule. Outcomes: 201 confirmed (with a signed receipt), 202 pending_approval (a human must decide; do not retry with a new key), or 422 order_blocked (nothing ordered; the problem body carries the blocked order and the rules that stopped it). A retry with the same idempotency key gets the same answer.","tags":["Ordering"],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Used when the body has no idempotencyKey","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PlaceOrderBody"},"example":{"offerJws":"<primary.jws from POST /api/v1/quotes>","mandateJws":"<jws from GET /api/v1/mandate>","idempotencyKey":"milk-2026-11-16-001"}}}},"responses":{"201":{"description":"Place an order from a signed offer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"202":{"description":"The order waits for the buyer's approver","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Conflicts with the current state or an earlier idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"422":{"description":"The mandate blocked the order; nothing was ordered","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/BlockedOrderProblem"}}}}},"security":[{"bearer":["order:place"]},{}],"x-audience":"agent"},"get":{"operationId":"listOrders","summary":"List one buyer's orders, newest first","tags":["Ordering"],"parameters":[{"name":"buyer","in":"query","required":false,"description":"Buyer id. Defaults to the signed-in approver's buyer, else the primary buyer. An agent token reads only its own buyer.","schema":{"type":"string"}}],"responses":{"200":{"description":"List one buyer's orders, newest first","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":[]},{"sandboxCookie":[]},{"approverSession":[]},{"operatorSession":[]}],"x-audience":"reader"}},"/api/v1/orders/{id}":{"get":{"operationId":"getOrder","summary":"Read an order and its status","description":"Needs an agent key (its own buyer's orders only), the browser's sandbox or a signed-in session; 401 without any.","tags":["Ordering"],"parameters":[{"name":"id","in":"path","required":true,"description":"Order id","schema":{"type":"string"}}],"responses":{"200":{"description":"Read an order and its status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":["order:place"]},{}],"x-audience":"agent"}},"/api/v1/approvals":{"get":{"operationId":"listPendingApprovals","summary":"One buyer's orders waiting for a human decision","tags":["Approvals"],"parameters":[{"name":"buyer","in":"query","required":false,"description":"Buyer id. Defaults to the signed-in approver's buyer, else the primary buyer. An agent token reads only its own buyer.","schema":{"type":"string"}}],"responses":{"200":{"description":"One buyer's orders waiting for a human decision","content":{"application/json":{"schema":{"$ref":"#/components/schemas/OrderList"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":[]},{"sandboxCookie":[]},{"approverSession":[]},{"operatorSession":[]}],"x-audience":"reader"}},"/api/v1/approvals/{orderId}":{"post":{"operationId":"decideApproval","summary":"Approve or reject a pending order (buyer console only)","description":"Refused for any request that carries an agent bearer token. Requires a signed approver session, which only the console sign-in issues. Another buyer's order is not found.","tags":["Approvals"],"parameters":[{"name":"orderId","in":"path","required":true,"description":"Order id","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApprovalDecisionRequest"}}}},"responses":{"200":{"description":"Approve or reject a pending order (buyer console only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Order"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"409":{"description":"Conflicts with the current state or an earlier idempotency key","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"approverSession":[]}],"x-audience":"human"}},"/api/v1/mandate":{"get":{"operationId":"getMandate","summary":"Read a buyer-signed mandate","description":"With an agent token, the mandate of that token's buyer.","tags":["Mandate"],"parameters":[{"name":"buyer","in":"query","required":false,"description":"Buyer id. Defaults to the signed-in approver's buyer, else the primary buyer. An agent token reads only its own buyer.","schema":{"type":"string"}}],"responses":{"200":{"description":"Read a buyer-signed mandate","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedMandate"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":[]},{"sandboxCookie":[]},{"approverSession":[]},{"operatorSession":[]}],"x-audience":"reader"},"put":{"operationId":"updateMandate","summary":"Change the signed-in approver's buyer's mandate (buyer console only)","tags":["Mandate"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MandatePatch"}}}},"responses":{"200":{"description":"Change the signed-in approver's buyer's mandate (buyer console only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignedMandate"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"approverSession":[]}],"x-audience":"human"}},"/api/v1/audit":{"get":{"operationId":"getAuditLog","summary":"Read the hash-chained audit log","description":"A browser in its sandbox or a signed-in person reads every entry. An agent key reads its own buyer's entries in full and every other entry as a stub (seq, at, event, prevHash, contentDigest, hash, signature, redacted: true), which is enough to verify the whole chain with the JWKS.","tags":["Audit"],"parameters":[{"name":"after","in":"query","required":false,"description":"Only entries with a higher seq","schema":{"type":"integer"}}],"responses":{"200":{"description":"Read the hash-chained audit log","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditLog"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":[]},{"sandboxCookie":[]},{"approverSession":[]},{"operatorSession":[]}],"x-audience":"reader"}},"/api/v1/audit/verify":{"get":{"operationId":"verifyAudit","summary":"Verify the chain and every signature","tags":["Audit"],"responses":{"200":{"description":"Verify the chain and every signature","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditVerification"}}}}},"security":[],"x-audience":"public"}},"/api/v1/audit/checkpoint":{"get":{"operationId":"getAuditCheckpoint","summary":"Signed statement of log size and head hash","tags":["Audit"],"responses":{"200":{"description":"Signed statement of log size and head hash","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Checkpoint"}}}}},"security":[],"x-audience":"public"}},"/api/v1/activity":{"get":{"operationId":"listActivity","summary":"Recent calls by one buyer's agents and by callers without a token","tags":["Audit"],"parameters":[{"name":"after","in":"query","required":false,"description":"Only activity after this id","schema":{"type":"string"}},{"name":"buyer","in":"query","required":false,"description":"Buyer id. Defaults to the signed-in approver's buyer, else the primary buyer. An agent token reads only its own buyer.","schema":{"type":"string"}}],"responses":{"200":{"description":"Recent calls by one buyer's agents and by callers without a token","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityList"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"bearer":[]},{"sandboxCookie":[]},{"approverSession":[]},{"operatorSession":[]}],"x-audience":"reader"}},"/api/v1/supplier/overview":{"get":{"operationId":"getSupplierOverview","summary":"The wholesaler's view of agent trade across every buyer","description":"Connected agents, demand, unmet demand with the estimated lost value, offers, orders by outcome, the mandate rules that fired and a 7-day series. Holds only what reached the wholesaler. Needs a signed-in session or the browser's sandbox (401 without either); refused for agent credentials.","tags":["Supplier"],"responses":{"200":{"description":"The wholesaler's view of agent trade across every buyer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SupplierOverview"}}}},"401":{"description":"No credential was sent (code credential_required), or the bearer token is not known","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"operatorSession":[]},{"approverSession":[]},{"sandboxCookie":[]}],"x-audience":"operator"}},"/api/v1/supplier/agents/{agentId}/suspension":{"post":{"operationId":"setAgentSuspension","summary":"Suspend or resume one agent (wholesaler operator only)","description":"While an agent is suspended, every call it makes over REST or MCP is refused with 403 and the problem code agent_suspended. Needs a wholesaler operator session (POST /api/v1/console/session with role operator: demo access in a sandbox, LAITURI_OPERATOR_PASSCODE in the shared workspace). No session, a buyer approver's session or any agent credential is refused with 403 and a signed audit entry. The suspension, the resumption and every refused call are signed audit entries that name the operator and say when demo access was used. Asking for the state the agent is already in changes nothing.","tags":["Supplier"],"parameters":[{"name":"agentId","in":"path","required":true,"description":"Agent id as the supplier overview lists it, for example agent:kaisla-menu-planner","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuspensionRequest"}}}},"responses":{"200":{"description":"Suspend or resume one agent (wholesaler operator only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SuspensionState"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"operatorSession":[]}],"x-audience":"operator"}},"/api/v1/demo/reset":{"post":{"operationId":"resetDemo","summary":"Start the caller's workspace over (console only)","description":"In a visitor's sandbox: a fresh copy of the seeded week, for that sandbox only, at the browser's word. In the shared workspace: needs the approver; clears orders, audit and activity, then writes the demo history again when LAITURI_SEED is on.","tags":["System"],"responses":{"200":{"description":"Start the caller's workspace over (console only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ResetResult"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[{"approverSession":[]}],"x-audience":"human"}},"/api/v1/workspace":{"get":{"operationId":"getWorkspace","summary":"The browser's workspace and its agent keys (console only)","description":"Each browser gets its own sandbox, an in-memory copy of the demo, named by an HttpOnly cookie. Its agent keys work as bearer tokens or as ?key= on /mcp, and calls with them land in that sandbox. A caller without a cookie sees the shared workspace and its fixed demo tokens. Refused for agent credentials.","tags":["System"],"responses":{"200":{"description":"The browser's workspace and its agent keys (console only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceInfo"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[],"x-audience":"public"},"post":{"operationId":"createWorkspace","summary":"Create a private sandbox for a client without a browser","description":"No credential needed. Answers 201 with the new sandbox's agent keys, one per demo buyer, and their MCP addresses; no cookie is set, so the keys are the only way in. An unused sandbox is dropped after the idle limit. Each client address may create a few per ten minutes (429 too_many_sandboxes with Retry-After). Refused for agent credentials.","tags":["System"],"responses":{"201":{"description":"Create a private sandbox for a client without a browser","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceInfo"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"429":{"description":"Too many requests from this address; Retry-After says when to try again","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[],"x-audience":"public"}},"/api/v1/workspace/view":{"post":{"operationId":"setWorkspaceView","summary":"Look at the shared workspace, or back at your sandbox (console only)","description":"The shared workspace is read-only for a browser unless it signs in with the passcode. The sandbox is kept meanwhile. Refused for agent credentials.","tags":["System"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceViewRequest"}}}},"responses":{"200":{"description":"Look at the shared workspace, or back at your sandbox (console only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WorkspaceInfo"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[],"x-audience":"public"}},"/api/v1/health":{"get":{"operationId":"getHealth","summary":"Service and upstream status","description":"Includes whether demo history was written at start-up, which engine runs free-text requests, and how many sandboxes are active.","tags":["System"],"responses":{"200":{"description":"Service and upstream status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Health"}}}}},"security":[],"x-audience":"public"}},"/api/v1/voice":{"get":{"operationId":"getVoice","summary":"Which voice the console speaks with (console only)","description":"Refused for agent credentials.","tags":["System"],"responses":{"200":{"description":"Which voice the console speaks with (console only)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VoiceSettings"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[],"x-audience":"public"}},"/api/v1/voice/speech":{"post":{"operationId":"speakText","summary":"Read a short text aloud as MP3 (console only)","description":"Uses ElevenLabs; the key stays on the server. Answers 404 voice_not_configured when the server has no ElevenLabs key. Refused for agent credentials.","tags":["System"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SpeechRequest"}}}},"responses":{"200":{"description":"Read a short text aloud as MP3 (console only)","content":{"audio/mpeg":{"schema":{"$ref":"#/components/schemas/SpeechAudio"}}}},"400":{"description":"The request does not match the schema","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"403":{"description":"Not allowed for this caller","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"404":{"description":"Not found","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}},"502":{"description":"The wholesaler API did not answer","content":{"application/problem+json":{"schema":{"$ref":"#/components/schemas/Problem"}}}}},"security":[],"x-audience":"public"}}},"components":{"schemas":{"WorkspaceInfo":{"type":"object","properties":{"kind":{"type":"string","enum":["sandbox","shared"],"description":"sandbox: the visitor's own in-memory copy of the demo; shared: the one persisted workspace the documented curl flows use"},"label":{"type":"string","description":"Public name; never the cookie secret"},"createdAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"expiresAt":{"description":"When an unused sandbox is dropped; every call moves it on. Absent for the shared workspace","type":"string"},"agentKeys":{"type":"array","items":{"type":"object","properties":{"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"description":"The agent the buyer's mandate names; calls with this key act as it"},"key":{"type":"string","description":"Bearer token for REST, or ?key= in the MCP address"},"mcpUrl":{"type":"string","description":"The MCP address with the key in it, for chat apps that take no header"}},"required":["buyer","agent","key","mcpUrl"]},"description":"One per demo buyer. A sandbox's keys are random and work only in that sandbox"},"mcpUrl":{"type":"string"}},"required":["kind","label","createdAt","agentKeys","mcpUrl"]},"WorkspaceViewRequest":{"type":"object","properties":{"view":{"type":"string","enum":["shared","sandbox"],"description":"shared: look at the shared workspace, read-only without the passcode; sandbox: back to your own"}},"required":["view"]},"Money":{"type":"object","properties":{"amount":{"type":"number","description":"Decimal amount, two-decimal precision"},"currency":{"type":"string","const":"EUR"}},"required":["amount","currency"]},"Product":{"type":"object","properties":{"id":{"type":"string","description":"Wholesaler product code"},"gtin":{"description":"GTIN of the sales unit","type":"string"},"name":{"type":"object","properties":{"en":{"type":"string"},"fi":{"type":"string"},"sv":{"type":"string"}},"required":["en","fi"]},"brand":{"type":"string"},"vendor":{"type":"string"},"category":{"type":"string","description":"Wholesaler category code"},"categoryGroup":{"type":"string","description":"Readable category group id, for example dairy"},"concept":{"description":"Normalised product type, for example milk","type":"string"},"origin":{"description":"ISO 3166-1 alpha-2 country of origin","type":"string"},"salesUnit":{"type":"string"},"baseUnit":{"type":"string"},"units":{"type":"array","items":{"type":"object","properties":{"unit":{"type":"string","description":"Wholesaler unit code, for example ST (piece), KI (case), PAL (pallet)"},"gtin":{"type":"string"},"sizeInBaseUnits":{"type":"number"}},"required":["unit","sizeInBaseUnits"]}},"lotSize":{"type":"number","description":"Order quantity step, in sales units"},"content":{"description":"Net content of one sales unit","type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["l","kg"]}},"required":["amount","unit"]},"temperature":{"type":"string","enum":["ambient","chilled","frozen"]},"allergens":{"type":"object","properties":{"contains":{"type":"array","items":{"type":"string"}},"mayContain":{"type":"array","items":{"type":"string"}},"freeFrom":{"type":"array","items":{"type":"string"}}},"required":["contains","mayContain","freeFrom"],"description":"Allergen ids from the allergen vocabulary, for example milk, gluten, nuts"},"claims":{"type":"array","items":{"type":"string"},"description":"Dietary and nutrition claims, for example lactose_free, vegan"},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and origin labels, for example eu_organic"},"substitutes":{"type":"array","items":{"type":"string"},"description":"Wholesaler-listed substitute product codes"},"co2PerKg":{"description":"Average kg CO2-eq per kg","type":"number"},"deliveryReliability":{"description":"Historic delivery reliability, 0 to 1","type":"number"}},"required":["id","name","vendor","category","categoryGroup","salesUnit","baseUnit","units","lotSize","allergens","claims","labels","substitutes"]},"SearchRequest":{"type":"object","properties":{"query":{"type":"string","minLength":1,"maxLength":300,"description":"What the buyer needs, in Finnish, Swedish or English"},"filters":{"type":"object","properties":{"claims":{"description":"Every listed claim must be present","type":"array","items":{"type":"string"}},"excludeAllergens":{"description":"Products that contain any of these are removed","type":"array","items":{"type":"string"}},"labels":{"type":"array","items":{"type":"string"}},"categoryGroup":{"type":"string"},"temperature":{"type":"string","enum":["ambient","chilled","frozen"]}}},"limit":{"default":10,"type":"integer","minimum":1,"maximum":50},"need":{"description":"How much is needed. Adds pack maths and an estimated total to each hit","type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0},"unit":{"type":"string","enum":["l","kg","sales_unit"]}},"required":["amount","unit"]},"deliverBy":{"description":"Latest acceptable delivery date. Defaults to the next working day","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"prefer":{"description":"How to rank products that all match clearly: price (default, lowest price per litre or kilo), reliability (highest delivery reliability) or sustainability (certified sustainability labels, then lower category-average CO2). Products orderable on time stay first, and an uncertain match keeps its relevance order","type":"string","enum":["price","reliability","sustainability"]}},"required":["query"]},"SearchResponse":{"type":"object","properties":{"query":{"type":"string"},"interpretation":{"type":"object","properties":{"concept":{"type":"string"},"claims":{"type":"array","items":{"type":"string"}},"confidence":{"type":"string","enum":["high","medium","low"]},"note":{"description":"Set when the request is ambiguous; says what to clarify","type":"string"}},"required":["claims","confidence"]},"hits":{"type":"array","items":{"type":"object","properties":{"product":{"$ref":"#/components/schemas/Product"},"score":{"type":"number"},"matched":{"type":"array","items":{"type":"string"},"description":"Why this product matched"},"facts":{"type":"object","properties":{"countryOfOrigin":{"description":"ISO 3166-1 alpha-2 country of origin, as the wholesaler records it","type":"string"},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and origin labels on the product, for example organic, msc or finnish_origin"},"deliveryReliability":{"description":"Share of past deliveries of this product that arrived in full and on time, 0 to 1","type":"number"},"categoryAverageCo2PerKg":{"description":"Average kg CO2-eq per kg for the product's category. A category average, not a measurement of this product","type":"number"}},"required":["labels"]},"offerPreview":{"type":"object","properties":{"unitsNeeded":{"description":"Sales units that cover the need, rounded up to the lot size","type":"number"},"contentPerUnit":{"type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["l","kg"]}},"required":["amount","unit"]},"unitPrice":{"description":"Net price per sales unit","$ref":"#/components/schemas/Money"},"comparisonPrice":{"type":"object","properties":{"amount":{"type":"number"},"per":{"type":"string","enum":["l","kg"]}},"required":["amount","per"]},"estimatedNet":{"description":"Net total for the need","$ref":"#/components/schemas/Money"},"availability":{"type":"object","properties":{"status":{"type":"string","enum":["available","partial","unavailable","cutoff_passed","blocked"]},"available":{"description":"Sales units in stock; absent when the wholesaler tracks no limit","type":"number"},"deliveryDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date, YYYY-MM-DD"},"orderBy":{"type":"string","description":"ISO 8601 timestamp with offset"}},"required":["status"]},"orderable":{"type":"boolean","description":"Can be ordered in full for delivery by the date"}},"required":["unitPrice","availability","orderable"],"description":"Indicative live price and availability. Only a signed offer from request_quote fixes the price."}},"required":["product","score","matched","facts"]}}},"required":["query","interpretation","hits"]},"QuoteRequest":{"type":"object","properties":{"lines":{"minItems":1,"maxItems":50,"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"quantity":{"type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0},"unit":{"type":"string","enum":["l","kg","sales_unit"]}},"required":["amount","unit"],"description":"Use l or kg to let the wholesaler convert to sales units"}},"required":["productId","quantity"]}},"deliverBy":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Latest acceptable delivery date"},"intent":{"description":"The buyer's original words, kept for the audit trail","type":"string","maxLength":500},"matchConfidence":{"type":"string","enum":["high","medium","low"]}},"required":["lines","deliverBy"]},"QuoteResponse":{"type":"object","properties":{"primary":{"description":"The request as asked, with honest availability","$ref":"#/components/schemas/SignedOffer"},"alternatives":{"type":"array","items":{"type":"object","properties":{"tier":{"type":"string","enum":["good","better","best"]},"meaning":{"type":"string","description":"One sentence: what this tier stands for, the same for every alternative of the tier"},"tradeoff":{"type":"string","description":"One sentence: what changes compared with the request"},"signed":{"$ref":"#/components/schemas/SignedOffer"}},"required":["tier","meaning","tradeoff","signed"]},"description":"Ranked ways to cover the full need when the primary cannot"}},"required":["primary","alternatives"]},"SignedOffer":{"type":"object","properties":{"offer":{"type":"object","properties":{"id":{"type":"string"},"issuer":{"type":"string"},"audience":{"description":"The workspace that issued the offer; an order elsewhere refuses it. Absent only on offers signed before workspaces were bound","type":"string"},"customerNumber":{"type":"string"},"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"description":"The buyer the offer was made for; only that buyer's mandate can order it"},"issuedAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"expiresAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"intent":{"type":"string"},"matchConfidence":{"type":"string","enum":["high","medium","low"]},"deliverBy":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date, YYYY-MM-DD"},"deliveryDate":{"description":"Date all lines arrive","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"orderBy":{"description":"Earliest cut-off across the lines","type":"string"},"fulfilment":{"type":"string","enum":["full","partial","substituted","late","unavailable"]},"lines":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"gtin":{"type":"string"},"name":{"type":"string"},"categoryGroup":{"type":"string"},"quantity":{"type":"number","description":"Quantity in sales units"},"salesUnit":{"type":"string"},"content":{"description":"Total net content of this line","type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["l","kg"]}},"required":["amount","unit"]},"unitPrice":{"description":"Fixed net price per sales unit","$ref":"#/components/schemas/Money"},"comparisonPrice":{"type":"object","properties":{"amount":{"type":"number"},"per":{"type":"string","enum":["l","kg"]}},"required":["amount","per"]},"taxRate":{"type":"number"},"lineNet":{"$ref":"#/components/schemas/Money"},"availability":{"type":"object","properties":{"status":{"type":"string","enum":["available","partial","unavailable","cutoff_passed","blocked"]},"requested":{"type":"number","description":"Sales units needed to cover the request"},"available":{"description":"Sales units the wholesaler counts in stock; absent when stock is untracked, because no count stands behind it","type":"number"},"reserved":{"description":"Sales units this workspace's own confirmed and waiting orders hold for that delivery date. The mock API does not decrement stock, so `available` is the wholesaler's figure minus these units","type":"number"},"stock":{"description":"untracked: the wholesaler keeps no stock count for this product, so the offered quantity is not checked against stock. Absent only on offers signed before this field was added","type":"string","enum":["tracked","untracked"]},"deliveryDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date, YYYY-MM-DD"},"orderBy":{"description":"Order cut-off for that delivery date","type":"string"},"logisticModel":{"type":"string","enum":["Stock","Terminal"]}},"required":["status","requested"]},"facts":{"description":"What the wholesaler vouches for about this product, signed with the offer. Absent only on offers signed before these facts were added","type":"object","properties":{"countryOfOrigin":{"description":"ISO 3166-1 alpha-2 country of origin, as the wholesaler records it","type":"string"},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and origin labels on the product, for example organic, msc or finnish_origin"},"deliveryReliability":{"description":"Share of past deliveries of this product that arrived in full and on time, 0 to 1","type":"number"},"categoryAverageCo2PerKg":{"description":"Average kg CO2-eq per kg for the product's category. A category average, not a measurement of this product","type":"number"}},"required":["labels"]},"substituteFor":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"reason":{"type":"string"},"likeForLike":{"type":"boolean"},"priceChangePct":{"type":"number"}},"required":["productId","name","reason","likeForLike","priceChangePct"]}},"required":["productId","name","categoryGroup","quantity","salesUnit","unitPrice","taxRate","lineNet","availability"]}},"totals":{"type":"object","properties":{"net":{"$ref":"#/components/schemas/Money"},"tax":{"$ref":"#/components/schemas/Money"},"gross":{"$ref":"#/components/schemas/Money"}},"required":["net","tax","gross"]},"notes":{"type":"array","items":{"type":"string"},"description":"Plain-language exceptions, for example a passed cut-off"},"requestCheck":{"description":"The wholesaler's comparison of the buyer's words (intent) with this offer, one entry per requirement, signed with the offer. Absent when no words were sent","type":"array","items":{"type":"object","properties":{"requirement":{"type":"string","enum":["product","claim","brand","attribute","quantity","date","budget","approval","wording"]},"asked":{"type":"string","description":"The requirement as the buyer's words state it"},"offered":{"type":"string","description":"What the offer gives for it, naming the product that would be delivered"},"result":{"type":"string","enum":["met","unmet","unchecked","substituted"]},"certain":{"type":"boolean","description":"True when the offer certainly fails the words (wrong product, a missing claim, far too little); the order is then refused, not sent to a person"}},"required":["requirement","asked","offered","result","certain"]}},"source":{"type":"object","properties":{"system":{"type":"string"},"pricedAt":{"type":"string","description":"ISO 8601 timestamp with offset"}},"required":["system","pricedAt"]}},"required":["id","issuer","customerNumber","buyer","issuedAt","expiresAt","matchConfidence","deliverBy","fulfilment","lines","totals","notes","source"]},"jws":{"type":"string","description":"Compact JWS, ES256, verifiable against the issuer's published JWKS"}},"required":["offer","jws"]},"PlaceOrderRequest":{"type":"object","properties":{"offerJws":{"type":"string","description":"The signed offer exactly as received"},"mandateJws":{"type":"string","description":"The buyer-signed mandate that authorises this agent"},"idempotencyKey":{"type":"string","minLength":8,"description":"Repeat the same key to retry safely"}},"required":["offerJws","mandateJws","idempotencyKey"]},"PlaceOrderBody":{"type":"object","properties":{"offerJws":{"type":"string","description":"The signed offer exactly as received"},"mandateJws":{"type":"string","description":"The buyer-signed mandate that authorises this agent"},"idempotencyKey":{"description":"Repeat the same key to retry safely. May be sent as the Idempotency-Key header instead.","type":"string","minLength":8}},"required":["offerJws","mandateJws"]},"Order":{"type":"object","properties":{"id":{"type":"string"},"status":{"type":"string","enum":["confirmed","pending_approval","rejected","blocked"]},"createdAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"updatedAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"mandateId":{"type":"string"},"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"offer":{"type":"object","properties":{"id":{"type":"string"},"issuer":{"type":"string"},"audience":{"description":"The workspace that issued the offer; an order elsewhere refuses it. Absent only on offers signed before workspaces were bound","type":"string"},"customerNumber":{"type":"string"},"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"],"description":"The buyer the offer was made for; only that buyer's mandate can order it"},"issuedAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"expiresAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"intent":{"type":"string"},"matchConfidence":{"type":"string","enum":["high","medium","low"]},"deliverBy":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date, YYYY-MM-DD"},"deliveryDate":{"description":"Date all lines arrive","type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$"},"orderBy":{"description":"Earliest cut-off across the lines","type":"string"},"fulfilment":{"type":"string","enum":["full","partial","substituted","late","unavailable"]},"lines":{"type":"array","items":{"type":"object","properties":{"productId":{"type":"string"},"gtin":{"type":"string"},"name":{"type":"string"},"categoryGroup":{"type":"string"},"quantity":{"type":"number","description":"Quantity in sales units"},"salesUnit":{"type":"string"},"content":{"description":"Total net content of this line","type":"object","properties":{"amount":{"type":"number"},"unit":{"type":"string","enum":["l","kg"]}},"required":["amount","unit"]},"unitPrice":{"description":"Fixed net price per sales unit","$ref":"#/components/schemas/Money"},"comparisonPrice":{"type":"object","properties":{"amount":{"type":"number"},"per":{"type":"string","enum":["l","kg"]}},"required":["amount","per"]},"taxRate":{"type":"number"},"lineNet":{"$ref":"#/components/schemas/Money"},"availability":{"type":"object","properties":{"status":{"type":"string","enum":["available","partial","unavailable","cutoff_passed","blocked"]},"requested":{"type":"number","description":"Sales units needed to cover the request"},"available":{"description":"Sales units the wholesaler counts in stock; absent when stock is untracked, because no count stands behind it","type":"number"},"reserved":{"description":"Sales units this workspace's own confirmed and waiting orders hold for that delivery date. The mock API does not decrement stock, so `available` is the wholesaler's figure minus these units","type":"number"},"stock":{"description":"untracked: the wholesaler keeps no stock count for this product, so the offered quantity is not checked against stock. Absent only on offers signed before this field was added","type":"string","enum":["tracked","untracked"]},"deliveryDate":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date, YYYY-MM-DD"},"orderBy":{"description":"Order cut-off for that delivery date","type":"string"},"logisticModel":{"type":"string","enum":["Stock","Terminal"]}},"required":["status","requested"]},"facts":{"description":"What the wholesaler vouches for about this product, signed with the offer. Absent only on offers signed before these facts were added","type":"object","properties":{"countryOfOrigin":{"description":"ISO 3166-1 alpha-2 country of origin, as the wholesaler records it","type":"string"},"labels":{"type":"array","items":{"type":"string"},"description":"Certificates and origin labels on the product, for example organic, msc or finnish_origin"},"deliveryReliability":{"description":"Share of past deliveries of this product that arrived in full and on time, 0 to 1","type":"number"},"categoryAverageCo2PerKg":{"description":"Average kg CO2-eq per kg for the product's category. A category average, not a measurement of this product","type":"number"}},"required":["labels"]},"substituteFor":{"type":"object","properties":{"productId":{"type":"string"},"name":{"type":"string"},"reason":{"type":"string"},"likeForLike":{"type":"boolean"},"priceChangePct":{"type":"number"}},"required":["productId","name","reason","likeForLike","priceChangePct"]}},"required":["productId","name","categoryGroup","quantity","salesUnit","unitPrice","taxRate","lineNet","availability"]}},"totals":{"type":"object","properties":{"net":{"$ref":"#/components/schemas/Money"},"tax":{"$ref":"#/components/schemas/Money"},"gross":{"$ref":"#/components/schemas/Money"}},"required":["net","tax","gross"]},"notes":{"type":"array","items":{"type":"string"},"description":"Plain-language exceptions, for example a passed cut-off"},"requestCheck":{"description":"The wholesaler's comparison of the buyer's words (intent) with this offer, one entry per requirement, signed with the offer. Absent when no words were sent","type":"array","items":{"type":"object","properties":{"requirement":{"type":"string","enum":["product","claim","brand","attribute","quantity","date","budget","approval","wording"]},"asked":{"type":"string","description":"The requirement as the buyer's words state it"},"offered":{"type":"string","description":"What the offer gives for it, naming the product that would be delivered"},"result":{"type":"string","enum":["met","unmet","unchecked","substituted"]},"certain":{"type":"boolean","description":"True when the offer certainly fails the words (wrong product, a missing claim, far too little); the order is then refused, not sent to a person"}},"required":["requirement","asked","offered","result","certain"]}},"source":{"type":"object","properties":{"system":{"type":"string"},"pricedAt":{"type":"string","description":"ISO 8601 timestamp with offset"}},"required":["system","pricedAt"]}},"required":["id","issuer","customerNumber","buyer","issuedAt","expiresAt","matchConfidence","deliverBy","fulfilment","lines","totals","notes","source"]},"decision":{"type":"object","properties":{"outcome":{"type":"string","enum":["auto_approved","needs_approval","blocked"]},"checks":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string","enum":["mandate.signature","mandate.validity","offer.signature","offer.expiry","audit.integrity","spend.per_order","spend.per_week","category.allowed","dietary.allergens","dietary.claims","substitution.policy","substitution.price","delivery.partial","delivery.deadline","match.confidence","request.words","product.first_time"]},"status":{"type":"string","enum":["pass","ask","block","skipped"]},"message":{"type":"string","description":"One short sentence a buyer understands"},"observed":{"type":"string"},"limit":{"type":"string"}},"required":["rule","status","message"]},"description":"Every rule that was evaluated, including the ones that passed"}},"required":["outcome","checks"]},"approval":{"type":"object","properties":{"decidedBy":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"decision":{"type":"string","enum":["approved","rejected"]},"decidedAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"note":{"type":"string","maxLength":500},"jws":{"type":"string","description":"The approver's signature over the order id, offer id and decision"}},"required":["decidedBy","decision","decidedAt","jws"]},"priceHold":{"description":"Present while or after the order waited for a person","type":"object","properties":{"heldUntil":{"type":"string","description":"The order cut-off for the delivery date, or the offer's own expiry when there is none"},"jws":{"type":"string","description":"Wholesaler signature (typ price-hold+jwt) over the order id, offer id, offer hash and heldUntil"}},"required":["heldUntil","jws"]},"refusal":{"description":"Present when the order was refused as invalid, not blocked by a mandate rule","type":"object","properties":{"code":{"type":"string","enum":["invalid_signature","not_issued_here"]},"message":{"type":"string"}},"required":["code","message"]},"receiptJws":{"description":"Wholesaler-signed order confirmation; present when confirmed","type":"string"},"simulated":{"type":"boolean","const":true,"description":"The challenge environment has no order endpoint; commitment is simulated here"},"seeded":{"description":"Demo history written at start-up through the real order path, not live use","type":"boolean","const":true}},"required":["id","status","createdAt","updatedAt","mandateId","agent","buyer","offer","decision","simulated"]},"OrderList":{"type":"object","properties":{"orders":{"type":"array","items":{"$ref":"#/components/schemas/Order"}}},"required":["orders"]},"ApprovalDecisionRequest":{"type":"object","properties":{"decision":{"type":"string","enum":["approved","rejected"]},"note":{"type":"string","maxLength":500}},"required":["decision"]},"Mandate":{"type":"object","properties":{"id":{"type":"string"},"version":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"buyer":{"type":"object","properties":{"id":{"type":"string","description":"Buyer organisation id"},"name":{"type":"string"},"customerNumber":{"type":"string","description":"Customer number at the wholesaler"}},"required":["id","name","customerNumber"]},"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"supplier":{"type":"string"},"validFrom":{"type":"string","description":"ISO 8601 timestamp with offset"},"validUntil":{"type":"string","description":"ISO 8601 timestamp with offset"},"limits":{"type":"object","properties":{"perOrder":{"description":"Largest order (net) the agent may place alone","$ref":"#/components/schemas/Money"},"perWeek":{"description":"Total net spend the agent may commit in one ISO week","$ref":"#/components/schemas/Money"}},"required":["perOrder","perWeek"]},"categories":{"type":"object","properties":{"allowed":{"type":"array","items":{"type":"string"},"description":"Category groups the agent may buy from"}},"required":["allowed"]},"dietary":{"type":"object","properties":{"excludeAllergens":{"type":"array","items":{"type":"string"},"description":"Hard rule: never buy products that contain these"},"blockMayContain":{"type":"boolean","description":"Treat 'may contain' as 'contains'"},"requiredClaims":{"type":"array","items":{"type":"string"},"description":"Hard rule: every product must carry these claims"}},"required":["excludeAllergens","blockMayContain","requiredClaims"]},"substitution":{"type":"object","properties":{"policy":{"type":"string","enum":["none","like_for_like","any_within_rules"]},"maxPriceIncreasePct":{"type":"number","minimum":0}},"required":["policy","maxPriceIncreasePct"]},"delivery":{"type":"object","properties":{"allowPartial":{"type":"boolean"},"maxDelayDays":{"type":"integer","minimum":0,"maximum":9007199254740991}},"required":["allowPartial","maxDelayDays"]},"approval":{"type":"object","properties":{"approver":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"firstTimeProducts":{"type":"boolean","description":"Ask a human before the first order of a product"},"requireRequestWords":{"default":true,"description":"Send an order to the approver when its offer carries no buyer's words, so it could not be checked against them","type":"boolean"}},"required":["approver","firstTimeProducts"]}},"required":["id","version","buyer","agent","supplier","validFrom","validUntil","limits","categories","dietary","substitution","delivery","approval"]},"SignedMandate":{"type":"object","properties":{"jws":{"type":"string","description":"Buyer-signed mandate JWT. Pass it unchanged as mandateJws when placing an order."},"mandate":{"$ref":"#/components/schemas/Mandate"}},"required":["jws","mandate"]},"MandatePatch":{"type":"object","properties":{"buyer":{"type":"object","properties":{"id":{"type":"string","description":"Buyer organisation id"},"name":{"type":"string"},"customerNumber":{"type":"string","description":"Customer number at the wholesaler"}}},"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}}},"supplier":{"type":"string"},"validFrom":{"type":"string","description":"ISO 8601 timestamp with offset"},"validUntil":{"type":"string","description":"ISO 8601 timestamp with offset"},"limits":{"type":"object","properties":{"perOrder":{"description":"Largest order (net) the agent may place alone","$ref":"#/components/schemas/Money"},"perWeek":{"description":"Total net spend the agent may commit in one ISO week","$ref":"#/components/schemas/Money"}}},"categories":{"type":"object","properties":{"allowed":{"type":"array","items":{"type":"string"},"description":"Category groups the agent may buy from"}}},"dietary":{"type":"object","properties":{"excludeAllergens":{"type":"array","items":{"type":"string"},"description":"Hard rule: never buy products that contain these"},"blockMayContain":{"type":"boolean","description":"Treat 'may contain' as 'contains'"},"requiredClaims":{"type":"array","items":{"type":"string"},"description":"Hard rule: every product must carry these claims"}}},"substitution":{"type":"object","properties":{"policy":{"type":"string","enum":["none","like_for_like","any_within_rules"]},"maxPriceIncreasePct":{"type":"number","minimum":0}}},"delivery":{"type":"object","properties":{"allowPartial":{"type":"boolean"},"maxDelayDays":{"type":"integer","minimum":0,"maximum":9007199254740991}}},"approval":{"type":"object","properties":{"approver":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"firstTimeProducts":{"type":"boolean","description":"Ask a human before the first order of a product"},"requireRequestWords":{"default":true,"description":"Send an order to the approver when its offer carries no buyer's words, so it could not be checked against them","type":"boolean"}}}},"additionalProperties":false},"AuditEntry":{"type":"object","properties":{"seq":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"at":{"type":"string","description":"ISO 8601 timestamp with offset"},"actor":{"type":"object","properties":{"type":{"type":"string","enum":["agent","human","supplier"]},"id":{"type":"string"},"name":{"type":"string"}},"required":["type","id","name"]},"event":{"type":"string","enum":["agent.connected","agent.suspended","agent.resumed","catalog.searched","offer.issued","order.requested","policy.evaluated","order.confirmed","order.blocked","approval.requested","approval.granted","approval.rejected","mandate.updated","access.refused"]},"summary":{"type":"string","description":"What happened and why, in one plain sentence"},"refs":{"type":"object","properties":{"offerId":{"type":"string"},"orderId":{"type":"string"},"mandateId":{"type":"string"},"buyerId":{"type":"string"}}},"data":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"Structured evidence for this step"},"seeded":{"description":"Demo history written at start-up; covered by the hash like every other field","type":"boolean","const":true},"prevHash":{"type":"string"},"contentDigest":{"description":"SHA-256 over the canonical JSON of actor, summary, refs, data and seeded. Absent only on entries written before digests were added","type":"string"},"hash":{"type":"string","description":"SHA-256 over the canonical JSON of seq, at, event, prevHash and contentDigest (before digests: of the whole entry)"},"signature":{"type":"string","description":"Wholesaler signature over seq and hash"}},"required":["seq","at","actor","event","summary","refs","data","prevHash","hash","signature"]},"AuditLog":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/AuditEntry"}}},"required":["entries"]},"AuditVerification":{"type":"object","properties":{"valid":{"type":"boolean"},"entries":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"head":{"description":"Hash of the newest entry","type":"string"},"brokenAt":{"description":"seq of the first entry that fails","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"type":"string"}},"required":["valid","entries"]},"Checkpoint":{"type":"object","properties":{"checkpoint":{"type":"object","properties":{"size":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Number of entries in the log"},"head":{"type":"string","description":"Hash of the newest entry"},"at":{"type":"string"}},"required":["size","head","at"]},"jws":{"type":"string","description":"Wholesaler signature over the checkpoint"}},"required":["checkpoint","jws"]},"AuditProof":{"type":"object","properties":{"verification":{"$ref":"#/components/schemas/AuditVerification"},"checkpoint":{"$ref":"#/components/schemas/Checkpoint"}},"required":["verification","checkpoint"]},"ActivityItem":{"type":"object","properties":{"id":{"type":"string"},"at":{"type":"string"},"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"buyer":{"description":"The buyer whose token the agent holds","type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"client":{"description":"The client application that made the call, for example Claude Code or curl","type":"string"},"kind":{"type":"string","enum":["tool","note"]},"tool":{"type":"string"},"title":{"type":"string"},"detail":{"type":"string"},"status":{"type":"string","enum":["ok","waiting","blocked","error"]},"refs":{"type":"object","properties":{"offerId":{"type":"string"},"orderId":{"type":"string"}}},"durationMs":{"type":"number"},"seeded":{"description":"Demo history written at start-up","type":"boolean","const":true}},"required":["id","at","agent","kind","title","status"]},"ActivityList":{"type":"object","properties":{"activity":{"type":"array","items":{"$ref":"#/components/schemas/ActivityItem"}}},"required":["activity"]},"Health":{"type":"object","properties":{"status":{"type":"string","enum":["ok","degraded"]},"mode":{"type":"string","enum":["live","recorded"],"description":"live calls the wholesaler API; recorded replays saved responses"},"upstream":{"type":"object","properties":{"ok":{"type":"boolean"},"latencyMs":{"type":"number"},"error":{"type":"string"}},"required":["ok"]},"catalog":{"type":"object","properties":{"ready":{"type":"boolean"},"size":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["ready","size"]},"seed":{"type":"object","properties":{"state":{"type":"string","enum":["seeded","skipped","failed","off"],"description":"seeded: history was written; skipped: the state already held orders or audit entries; failed: writing it failed; off: LAITURI_SEED=off"},"at":{"description":"When the history was written; set when seeded","type":"string"},"requests":{"description":"Buyer requests written; set when seeded","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"orders":{"description":"Orders among them; set when seeded","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"description":"Why nothing was written; set when skipped or failed","type":"string"}},"required":["state"],"description":"Whether demo history was written at start-up (LAITURI_SEED)"},"agent":{"type":"object","properties":{"engine":{"type":"string","enum":["claude","gemini","scripted"],"description":"scripted needs no model key"},"model":{"description":"The model name; set for claude and gemini","type":"string"}},"required":["engine"],"description":"Which engine runs free-text requests from the console (LAITURI_AGENT_ENGINE and the model keys)"},"sandboxes":{"type":"object","properties":{"active":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sandboxes used within the idle limit"},"max":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"At most this many live at once (LAITURI_SANDBOX_MAX); the least recently used is dropped first"},"idleMinutes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"An unused sandbox is replaced by a fresh copy after this long (LAITURI_SANDBOX_IDLE_MINUTES)"},"template":{"type":"object","properties":{"state":{"type":"string","enum":["seeded","skipped","failed","off"],"description":"seeded: the template holds the demo week; failed: writing it failed, sandboxes start empty; off: LAITURI_SEED=off"},"at":{"type":"string"},"requests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"orders":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"type":"string"}},"required":["state"],"description":"The seeded workspace every sandbox starts as a copy of"}},"required":["active","max","idleMinutes","template"],"description":"Per-browser sandboxes: how many are active and what they are copied from"},"store":{"type":"object","properties":{"recovery":{"description":"Set when the saved state could not be read at start-up: the file was kept under keptAs and the service started empty","type":"object","properties":{"keptAs":{"type":"string"},"reason":{"type":"string"},"at":{"type":"string"}},"required":["keptAs","reason","at"]},"lastSaveError":{"description":"Set while the last delayed save of offers, activity or demand failed","type":"string"}},"description":"The saved state on disk. Orders, approvals, mandate changes and audit entries are saved before their call returns"},"time":{"type":"string"}},"required":["status","mode","upstream","catalog","seed","agent","sandboxes","store","time"]},"ResetResult":{"type":"object","properties":{"ok":{"type":"boolean","const":true},"detail":{"type":"string"},"seed":{"type":"object","properties":{"state":{"type":"string","enum":["seeded","skipped","failed","off"],"description":"seeded: history was written; skipped: the state already held orders or audit entries; failed: writing it failed; off: LAITURI_SEED=off"},"at":{"description":"When the history was written; set when seeded","type":"string"},"requests":{"description":"Buyer requests written; set when seeded","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"orders":{"description":"Orders among them; set when seeded","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"reason":{"description":"Why nothing was written; set when skipped or failed","type":"string"}},"required":["state"],"description":"Whether demo history was written at start-up (LAITURI_SEED)"}},"required":["ok","detail"]},"SupplierOverview":{"type":"object","properties":{"generatedAt":{"type":"string","description":"ISO 8601 timestamp with offset"},"includesSeeded":{"type":"boolean","description":"True when part of the figures comes from seeded demo history"},"agents":{"type":"array","items":{"type":"object","properties":{"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"clients":{"type":"array","items":{"type":"string"},"description":"Client applications seen for this agent, most recent first"},"firstSeen":{"type":"string","description":"ISO 8601 timestamp with offset"},"lastSeen":{"type":"string","description":"ISO 8601 timestamp with offset"},"calls":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"quotes":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"orders":{"type":"object","properties":{"confirmed_auto":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"confirmed_after_approval":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"waiting":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"rejected":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"blocked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Stopped by one of the buyer's mandate rules"},"refused":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Refused as invalid: a signature did not verify, or the offer or mandate was not issued here"}},"required":["confirmed_auto","confirmed_after_approval","waiting","rejected","blocked","refused"]},"confirmedNet":{"type":"number","description":"Net EUR of confirmed orders"},"status":{"type":"string","enum":["active","idle","suspended"],"description":"suspended: the wholesaler refuses every call by this agent; active: a call in the last 24 hours; else idle"},"suspendedAt":{"description":"When the wholesaler suspended the agent; set while suspended","type":"string"},"reason":{"description":"Why the wholesaler suspended the agent, when it said","type":"string"}},"required":["agent","clients","firstSeen","lastSeen","calls","quotes","orders","confirmedNet","status"]}},"demand":{"type":"object","properties":{"requests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"topConcepts":{"type":"array","items":{"type":"object","properties":{"concept":{"type":"string"},"requests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"quantity":{"type":"array","items":{"type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0},"unit":{"type":"string","enum":["l","kg","sales_unit"]}},"required":["amount","unit"]},"description":"Total quantity asked, one entry per unit"}},"required":["concept","requests","quantity"]}}},"required":["requests","topConcepts"]},"unmet":{"type":"array","items":{"type":"object","properties":{"requestId":{"type":"string"},"at":{"type":"string","description":"ISO 8601 timestamp with offset"},"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"client":{"type":"string"},"words":{"description":"The buyer's words","type":"string"},"concept":{"type":"string"},"quantity":{"type":"object","properties":{"amount":{"type":"number","exclusiveMinimum":0},"unit":{"type":"string","enum":["l","kg","sales_unit"]}},"required":["amount","unit"]},"deliverBy":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date, YYYY-MM-DD"},"reason":{"type":"string","enum":["short_stock","cutoff_passed","not_found"]},"reasonText":{"type":"string"},"estimatedLostNet":{"description":"Net EUR the wholesaler could not sell; absent when no price is known","type":"number"},"seeded":{"type":"boolean","const":true}},"required":["requestId","at","agent","client","reason","reasonText"]},"description":"Requests not met in full and on time, or that found nothing; newest first"},"offers":{"type":"object","properties":{"signed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Every signed offer, primary or alternative"},"ordered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"expiredUnused":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"open":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"conversionRate":{"type":"number","description":"ordered / signed, 0 to 1"},"medianSecondsToOrder":{"type":"number"}},"required":["signed","ordered","expiredUnused","open","conversionRate"]},"orders":{"type":"object","properties":{"total":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]},"byOutcome":{"type":"object","properties":{"confirmed_auto":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]},"confirmed_after_approval":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]},"waiting":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]},"rejected":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]},"blocked":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]},"refused":{"type":"object","properties":{"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"net":{"type":"number"}},"required":["count","net"]}},"required":["confirmed_auto","confirmed_after_approval","waiting","rejected","blocked","refused"]},"medianSecondsRequestToConfirmed":{"description":"Median seconds from the first search of a request (its first quote when it began with one) to an order that confirmed without a person; an order on an alternative offer counts from the request whose quote issued it. Only live requests and orders count: seeded demo history runs on a backdated clock, so it is left out. Absent until one such order exists.","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["total","byOutcome"]},"rules":{"type":"array","items":{"type":"object","properties":{"rule":{"type":"string","enum":["mandate.signature","mandate.validity","offer.signature","offer.expiry","audit.integrity","spend.per_order","spend.per_week","category.allowed","dietary.allergens","dietary.claims","substitution.policy","substitution.price","delivery.partial","delivery.deadline","match.confidence","request.words","product.first_time"]},"title":{"type":"string"},"tier":{"type":"string","enum":["ask","block"]},"count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["rule","title","tier","count"]},"description":"Mandate rules that held or stopped orders, most frequent first"},"daily":{"type":"array","items":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Calendar date in Helsinki"},"requests":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Requests that started that day: a search, or a quote that began one"},"ordered":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Of that day's requests, those that led to at least one order; never more than requests"},"orders":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Orders placed that day; one request can lead to several"},"confirmed":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Orders placed that day that are confirmed"},"confirmedNet":{"type":"number","description":"Net EUR of the orders placed that day that are confirmed"}},"required":["date","requests","ordered","orders","confirmed","confirmedNet"]},"description":"The last 7 days, oldest first"}},"required":["generatedAt","includesSeeded","agents","demand","unmet","offers","orders","rules","daily"]},"SuspensionRequest":{"type":"object","properties":{"suspended":{"type":"boolean","description":"true suspends the agent, false resumes it"},"reason":{"description":"Why, in one sentence; kept in the signed audit entry","type":"string","minLength":1,"maxLength":300}},"required":["suspended"]},"SuspensionState":{"type":"object","properties":{"agent":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"buyer":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name"]},"suspended":{"type":"boolean"},"suspendedAt":{"description":"Set while suspended","type":"string"},"reason":{"type":"string"}},"required":["agent","suspended"]},"VoiceSettings":{"type":"object","properties":{"tts":{"type":"string","enum":["elevenlabs","browser"],"description":"elevenlabs: POST /api/v1/voice/speech speaks; browser: use the browser's own voices"}},"required":["tts"]},"SpeechRequest":{"type":"object","properties":{"text":{"type":"string","minLength":1,"maxLength":600,"description":"The text to read aloud"}},"required":["text"]},"SpeechAudio":{"type":"string","description":"MP3 audio of the text"},"Problem":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"detail":{"type":"string","description":"What went wrong, in one plain sentence"},"code":{"type":"string","description":"Stable machine-readable error code"},"violations":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"]}}},"required":["type","title","status","detail","code"]},"BlockedOrderProblem":{"type":"object","properties":{"type":{"type":"string"},"title":{"type":"string"},"status":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"detail":{"type":"string","description":"What went wrong, in one plain sentence"},"code":{"type":"string","description":"Stable machine-readable error code"},"violations":{"type":"array","items":{"type":"object","properties":{"path":{"type":"string"},"message":{"type":"string"}},"required":["path","message"]}},"order":{"description":"The blocked order as recorded: its decision lists every rule that was checked and the ones that blocked it","$ref":"#/components/schemas/Order"}},"required":["type","title","status","detail","code","order"]}},"securitySchemes":{"bearer":{"type":"http","scheme":"bearer","description":"Demo: POST /api/v1/workspace creates a private sandbox and returns one agent key per demo buyer; a key works only in its sandbox and reads only its own buyer's records. The shared demo workspace has fixed public tokens: demo-agent-token (Nordic Harbour Hotels), demo-kaisla-token (Kaisla School Kitchens), demo-laine-token (Ravintola Laine). Production: OAuth 2.1 access tokens with scopes catalog:read, quote:create, order:place."},"sandboxCookie":{"type":"apiKey","in":"cookie","name":"laituri_workspace","description":"The browser's own sandbox, set on its first page load. It reads everything in that sandbox. Never issued to a client without a browser."},"approverSession":{"type":"apiKey","in":"cookie","name":"laituri_approver","description":"A buyer approver's signed session from POST /api/v1/console/session, valid only in the workspace that issued it."},"operatorSession":{"type":"apiKey","in":"cookie","name":"laituri_operator","description":"The wholesaler operator's signed session from POST /api/v1/console/session, valid only in the workspace that issued it."}}},"x-assumptions":["Orders are simulated. The wholesaler test API has no order endpoint, so every order carries simulated: true.","Cut-off rule: a delivery date whose order closing time has passed is not orderable; the offer reports cutoff_passed.","Untracked stock sentinel: an available amount of 9999999999 or 999999 means the wholesaler does not track stock; the offer line says stock: untracked and gives no available count."]}