{"openapi":"3.1.0","info":{"title":"snuepi","version":"1.0.0","description":"Find sellers, see fixed prices and rules, get binding quotes, book or order, and follow requests through to a signed deal — no API key required. Classify the need against /taxonomy first. B2B buyers: one call to every matching supplier with POST /api/directory/v1/quote-requests (buyer key from /developers)."},"paths":{"/api/directory/v1/products":{"get":{"operationId":"searchProducts","summary":"Search items across every seller by need, SKU or GTIN. List prices excl. VAT, volume discounts, availability (never stock counts) and the seller. Cheapest best match first.","parameters":[{"name":"query","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"name":"postal_code","in":"query","schema":{"type":"string"}},{"name":"country","in":"query","schema":{"type":"string"}},{"name":"max_price_ex_vat","in":"query","schema":{"type":"number"}},{"name":"in_stock_only","in":"query","schema":{"type":"boolean"}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":25}}]}},"/api/directory/v1/providers/{slug}/orders":{"post":{"operationId":"buy","summary":"Buy from one seller in one call, at list price with published volume discounts, under the seller's rules. Send a snuepi agent key or the customer's mandate token (Authorization: Bearer) to buy on invoice, or a delegated payment token. Returns status completed (order + signed deal record), needs_identity, needs_payment or not_possible — always with what to do next.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["items"],"properties":{"items":{"type":"array","items":{"type":"object","required":["id","quantity"],"properties":{"id":{"type":"string","description":"item_id from searchProducts, or the SKU"},"quantity":{"type":"number"}}}},"buyer":{"type":"object","properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"}}},"delivery":{"type":"object","properties":{"name":{"type":"string"},"address":{"type":"string"},"postal_code":{"type":"string"},"country":{"type":"string"},"date":{"type":"string","format":"date"}}},"payment_token":{"type":"string"},"payment_provider":{"type":"string"},"idempotency_key":{"type":"string"}}}}}}}},"/api/directory/v1/orders/{id}":{"get":{"operationId":"getOrder","summary":"Order status, with the seller's own order number once it is in their system.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/directory/v1/lookup":{"get":{"operationId":"checkBusiness","summary":"Before you phone or book a business: its published agent terms, found by the phone number you would call or its website. found=false means no published terms.","parameters":[{"name":"phone","in":"query","schema":{"type":"string"}},{"name":"website","in":"query","schema":{"type":"string"}}]}},"/api/directory/v1/calls":{"post":{"operationId":"registerCall","summary":"Register a phone call before you make it. The business's terms decide: a six-digit code to say first (staff check it at /check), or the reason not to call and how to book instead. Send an agent key or the customer's mandate so staff see a verified principal.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"provider":{"type":"string"},"phone":{"type":"string"},"website":{"type":"string"},"purpose":{"type":"string"},"on_behalf_of":{"type":"string"},"agent_name":{"type":"string"}}}}}}}},"/api/directory/v1/taxonomy":{"get":{"operationId":"getTaxonomy","summary":"Canonical service categories with descriptions and example issues, for classifying a photo or description."}},"/api/directory/v1/providers":{"get":{"operationId":"searchProviders","summary":"Find listed sellers offering a service, optionally in a postal code area. Ranked by completed bookings and orders through snuepi.","parameters":[{"name":"service","in":"query","required":true,"schema":{"type":"string","enum":["restaurant","cleaning_commercial","cleaning_home","cleaning_move_out","window_cleaning","facility_services","grounds","plumbing","electrical","painting","carpentry","roofing","installation","maintenance","moving","waste","pest_control","locksmith","it_support","vehicle_services","pet_care","hair","beauty","fitness","training","events","other"]}},{"name":"postal_code","in":"query","required":false,"schema":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9 -]{1,9}$"}}]}},"/api/directory/v1/providers/{slug}":{"get":{"operationId":"getProvider","summary":"One seller with its offerings, rules and trust metrics.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/directory/v1/providers/{slug}/requests":{"post":{"operationId":"createRequest","summary":"Send a free-text request to a seller. Answered within the seller's rules (binding, indicative or reviewed by the owner); returns a status_token to follow it.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["description","name","address"],"properties":{"description":{"type":"string","minLength":10,"maxLength":4000},"service_type":{"type":"string","enum":["restaurant","cleaning_commercial","cleaning_home","cleaning_move_out","window_cleaning","facility_services","grounds","plumbing","electrical","painting","carpentry","roofing","installation","maintenance","moving","waste","pest_control","locksmith","it_support","vehicle_services","pet_care","hair","beauty","fitness","training","events","other"]},"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"postal_code":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9 -]{1,9}$"}}}}}}}},"/api/directory/v1/requests/{token}":{"get":{"operationId":"getRequestStatus","summary":"Status of a request. Once the seller has quoted, includes the quote and how to accept it.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/directory/v1/categories":{"get":{"operationId":"listCategories","summary":"All service categories (any local service business), with descriptions and example needs."}},"/api/directory/v1/providers/{slug}/offerings":{"get":{"operationId":"getOfferings","summary":"Bookable offerings with fixed prices, booking rules and trust metrics computed from transactions through snuepi.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/directory/v1/providers/{slug}/quote":{"post":{"operationId":"getQuote","summary":"Binding price for an offering (valid 30 minutes). Returns quote_token for booking.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["offering_id"],"properties":{"offering_id":{"type":"integer"},"quantity":{"type":"number","description":"Hours or units for hourly/per-unit offerings."},"postal_code":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9 -]{1,9}$"},"options":{"type":"object","additionalProperties":{"type":"string"},"description":"For offerings with options: the customer’s choice for each, e.g. {\"Hair length\":\"Long\"}. Missing choices give 400 options_required with the list."}}}}}}}},"/api/directory/v1/providers/{slug}/availability":{"get":{"operationId":"getAvailability","summary":"Available start times for an offering, in the seller’s local time (see timezone).","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}},{"name":"offering_id","in":"query","required":true,"schema":{"type":"integer"}},{"name":"quantity","in":"query","schema":{"type":"number"}},{"name":"from_date","in":"query","schema":{"type":"string","format":"date"}},{"name":"days","in":"query","schema":{"type":"integer","maximum":30}}]}},"/api/directory/v1/providers/{slug}/bookings":{"post":{"operationId":"createBooking","summary":"Book an available time with a quote_token. Confirmed at once, after the customer confirms by email, or after the provider approves — see status.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["quote_token","start","customer"],"properties":{"quote_token":{"type":"string"},"start":{"type":"string","example":"2026-11-03 10:00"},"notes":{"type":"string"},"agent_name":{"type":"string"},"details":{"type":"object","description":"What the business requires (its agent policy must_provide): party_size, allergies, access_instructions, pets, parking, photos."},"customer":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"phone":{"type":"string"},"address":{"type":"string"},"postal_code":{"type":"string"}}}}}}}}}},"/api/directory/v1/bookings/{token}":{"get":{"operationId":"getBooking","summary":"Booking status.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/directory/v1/bookings/{token}/cancel":{"post":{"operationId":"cancelBooking","summary":"Cancel before the provider's cancellation deadline.","parameters":[{"name":"token","in":"path","required":true,"schema":{"type":"string"}}]}},"/api/directory/v1/buyers":{"post":{"operationId":"createBuyer","summary":"Sign up as a buyer. Returns a test key immediately; the live key arrives when you confirm your email.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email"],"properties":{"name":{"type":"string"},"email":{"type":"string"},"organization_number":{"type":"string","description":"Verified automatically when your email domain matches the company website in its national business register."},"webhook_url":{"type":"string"}}}}}}}},"/api/directory/v1/buyers/me":{"get":{"operationId":"getBuyer","summary":"Your account, trust level, webhook and usage.","security":[{"buyerKey":[]}]},"patch":{"operationId":"updateBuyer","summary":"Set webhook_url, name, or rotate_webhook_secret.","security":[{"buyerKey":[]}]}},"/api/directory/v1/buyers/me/webhook/test":{"post":{"operationId":"testWebhook","summary":"Send a signed ping event to your webhook now and see the response.","security":[{"buyerKey":[]}]}},"/api/directory/v1/events":{"get":{"operationId":"listEvents","summary":"Recent events (the same ones sent to your webhook), with delivery status.","security":[{"buyerKey":[]}]}},"/api/directory/v1/quote-requests":{"post":{"operationId":"createQuoteRequest","summary":"Ask every matching supplier at once. Suppliers that carry at least one item and deliver to the postal code answer within their own rules; quotes come back ranked.","security":[{"buyerKey":[]}],"parameters":[{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Retry safely: the same key returns the same response for 24 hours."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","required":["quantity"],"properties":{"sku":{"type":"string"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string","example":"stk"}}},"maxItems":50},"text":{"type":"string","description":"Free text instead of items; it is read into items for you."},"delivery":{"type":"object","properties":{"date":{"type":"string","format":"date"},"postal_code":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9 -]{1,9}$"},"address":{"type":"string"}}},"buyer":{"type":"object","properties":{"name":{"type":"string"},"organization_number":{"type":"string"},"email":{"type":"string"}}},"payment_terms":{"type":"string","enum":["forskudd","ved_fullfort","faktura_14","faktura_30"]},"reference":{"type":"string"},"agent_name":{"type":"string"},"target_total_ex_vat":{"type":"number","description":"Your target total. Verified buyers get concessions toward it within each supplier's own discount cap and price floor (quote.pricing.negotiation says target_met or best_within_limits). Repeated probing is declined."},"suppliers":{"type":"array","items":{"type":"string"},"description":"Only these supplier slugs."},"max_suppliers":{"type":"integer","default":10,"maximum":25}}}}}}},"get":{"operationId":"listQuoteRequests","summary":"Your recent quote requests.","security":[{"buyerKey":[]}]}},"/api/directory/v1/quote-requests/{id}":{"get":{"operationId":"getQuoteRequest","summary":"The quote request with every supplier's current answer, ranked.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"qr_…"}]}},"/api/directory/v1/suppliers/{slug}/rfqs":{"post":{"operationId":"createRfq","summary":"Ask one supplier. No key needed; a key (or a signed request) decides how binding the answer is.","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"},"description":"Supplier slug"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Retry safely: the same key returns the same response for 24 hours."}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"items":{"type":"array","items":{"type":"object","required":["quantity"],"properties":{"sku":{"type":"string"},"description":{"type":"string"},"quantity":{"type":"number"},"unit":{"type":"string","example":"stk"}}},"maxItems":50},"text":{"type":"string","description":"Free text instead of items; it is read into items for you."},"delivery":{"type":"object","properties":{"date":{"type":"string","format":"date"},"postal_code":{"type":"string","pattern":"^[A-Za-z0-9][A-Za-z0-9 -]{1,9}$"},"address":{"type":"string"}}},"buyer":{"type":"object","properties":{"name":{"type":"string"},"organization_number":{"type":"string"},"email":{"type":"string"}}},"payment_terms":{"type":"string","enum":["forskudd","ved_fullfort","faktura_14","faktura_30"]},"reference":{"type":"string"},"agent_name":{"type":"string"},"target_total_ex_vat":{"type":"number","description":"Your target total. Verified buyers get concessions toward it within each supplier's own discount cap and price floor (quote.pricing.negotiation says target_met or best_within_limits). Repeated probing is declined."}}}}}}}},"/api/directory/v1/rfqs/{id}":{"get":{"operationId":"getRfq","summary":"One supplier's answer: status, quote, signed JWS.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"RFQ id"}]}},"/api/directory/v1/rfqs/{id}/accept":{"post":{"operationId":"acceptRfq","summary":"Accept a binding quote. Returns a deal record signed by snuepi (and by you, if you sign the request with Web Bot Auth). If your company has a purchasing policy or you use an agent key, the acceptance is checked first: a breach returns 403 policy_brudd; an amount over the limit (or an agent in shadow mode) returns status approval_required, and a person decides. The deal record then carries buyer_controls.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"RFQ id"},{"name":"Idempotency-Key","in":"header","required":false,"schema":{"type":"string","maxLength":200},"description":"Retry safely: the same key returns the same response for 24 hours."}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"accepted_by":{"type":"string"},"purchase_order":{"type":"string"}}}}}}}},"/api/directory/v1/rfqs/{id}/simulate":{"post":{"operationId":"simulateRfq","summary":"Test mode only: act as the supplier (approve, decline, deliver) to try the full flow and your webhooks.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"RFQ id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["action"],"properties":{"action":{"type":"string","enum":["approve","decline","deliver"]}}}}}}}},"/api/directory/v1/rfqs/{id}/outcome":{"post":{"operationId":"reportOutcome","summary":"Report how the delivery went. Builds the supplier reliability score every buyer is ranked against — use the key that sent the request.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"RFQ id"}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"price_as_quoted":{"type":"boolean"},"as_agreed":{"type":"boolean"},"delivered":{"type":"boolean","description":"Only false: the order never arrived (2 days after the promised date)."},"comment":{"type":"string"}}}}}}}},"/api/directory/v1/buyers/me/policy":{"get":{"operationId":"getPurchasingPolicy","summary":"Your purchasing policy: competing quotes required, the amount that needs a person, the amount that needs a signed acceptance, and supplier requirements.","security":[{"buyerKey":[]}]},"put":{"operationId":"setPurchasingPolicy","summary":"Set the purchasing policy (buyer key only; agent keys cannot change it). Every change is versioned, and the version in force is stamped on each purchase.","security":[{"buyerKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"min_quotes":{"type":"integer","minimum":1,"maximum":10},"approval_above":{"type":"number","nullable":true},"signature_above":{"type":"number","nullable":true},"supplier_requirements":{"type":"array","items":{"type":"string","enum":["verified_company","proven_reliable"]}}}}}}}}},"/api/directory/v1/buyers/me/agents":{"get":{"operationId":"listAgents","summary":"Your agent keys, their limits, spend this month, and shadow-mode record (how often a person agreed with the agent).","security":[{"buyerKey":[]}]},"post":{"operationId":"createAgent","summary":"Create an agent key (snp_ag_…) with its own limits. New agents start in shadow mode: a person decides every purchase until you grant autonomy.","security":[{"buyerKey":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string"},"limit_per_purchase":{"type":"number"},"limit_per_month":{"type":"number"},"mode":{"type":"string","enum":["shadow","autonomous"]}}}}}}}},"/api/directory/v1/buyers/me/agents/{id}":{"patch":{"operationId":"updateAgent","summary":"Change an agent's mode or limits (buyer key only).","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"agt_…"}]}},"/api/directory/v1/buyers/me/agents/{id}/revoke":{"post":{"operationId":"revokeAgent","summary":"Revoke an agent key immediately; its pending approvals expire.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"agt_…"}]}},"/api/directory/v1/approvals":{"get":{"operationId":"listApprovals","summary":"Purchases waiting for a person (buyer key only). ?status=all for history.","security":[{"buyerKey":[]}]}},"/api/directory/v1/approvals/{id}/approve":{"post":{"operationId":"approvePurchase","summary":"Approve: the acceptance runs again with every check, and the approver is recorded.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"apr_…"}],"requestBody":{"content":{"application/json":{"schema":{"type":"object","properties":{"approved_by":{"type":"string"}}}}}}}},"/api/directory/v1/approvals/{id}/decline":{"post":{"operationId":"declinePurchase","summary":"Decline a pending purchase.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"apr_…"}]}},"/api/directory/v1/rfqs/{id}/audit":{"get":{"operationId":"getAuditRecord","summary":"The signed audit record for one purchase: authority, policy at the time, competing quotes, supplier checks, approval, commitment, delivery, payment, and every attempt the agent made.","security":[{"buyerKey":[]}],"parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"RFQ id"}]}},"/api/directory/v1/audit/purchases.csv":{"get":{"operationId":"exportPurchases","summary":"Every accepted purchase in a period as CSV, for the auditor (buyer key only).","security":[{"buyerKey":[]}],"parameters":[{"name":"from","in":"query","schema":{"type":"string","format":"date"}},{"name":"to","in":"query","schema":{"type":"string","format":"date"}}]}},"/api/directory/v1/audit/log":{"get":{"operationId":"getPurchaseLog","summary":"Everything your agents did, including blocked attempts (buyer key only).","security":[{"buyerKey":[]}]}},"/api/directory/v1/records/{id}":{"get":{"operationId":"getDealRecord","summary":"A signed deal record, verifiable against /.well-known/snuepi-keys.json.","parameters":[{"name":"id","in":"path","required":true,"schema":{"type":"string"},"description":"Deal record id"}]}}},"components":{"securitySchemes":{"buyerKey":{"type":"http","scheme":"bearer","description":"snp_og_… (live), snp_test_… (test mode), or snp_ag_… (an agent key under a buyer, with its own spending limits). Create buyer keys at /developers or with POST /api/directory/v1/buyers; agent keys with POST /api/directory/v1/buyers/me/agents."}}}}