{"openapi":"3.1.0","info":{"title":"Sunny API v1","description":"The versioned Sunny API. `/v1` changes are **additive only**: new endpoints,\nnew optional request fields, new response fields, new enum values, including\na new error `type` or `code`. A removal is announced with `Deprecation` and\n`Sunset` headers at least six months ahead. Operations marked **beta**\n(`x-sunny-stability: beta`) may still\nchange shape; everything else is held to the additive rule by CI.\n\n## Client contract\n\nIgnore response fields you don't recognise, and handle enum values you\ndon't recognise (error `type`s and `code`s included). That is what lets\nnew fields and values ship without a new version.\n\n## Authentication\n\nSend an API key as `x-api-key` (`sunny_wsk_…` workspace or `sunny_sk_…`\npersonal), or an OAuth 2.1 Bearer token. Cookie sessions and the\nfirst-party shared secret are not accepted on `/v1`. `/v1` is\nserver-to-server: browsers get no CORS allowance.\n\n## Conventions\n\n- Ids are opaque strings; don't parse them.\n- Fields are snake_case. Timestamps are ISO 8601 in UTC.\n- A field with no value is omitted, never `null`.\n- Lists are `{ data, next_cursor? }`. Pass `next_cursor` back as `cursor`;\n  it is omitted on the last page. `limit` is 1-100, default 25.\n- Every response carries `X-Request-Id`. Quote it when reporting a problem.\n\n## Errors\n\nEvery error is `{ error: { type, code, message, param?, request_id } }`.\nThe documented `type` values (`invalid_request_error`,\n`authentication_error`, `permission_error`, `idempotency_error`,\n`rate_limit_error`, `api_error`) are the current set. New types may be\nadded; clients must handle a `type` they don't recognise. `code` is\nspecific and grows the same way. Neither is renamed or repurposed.\n`param` names the request field at fault when there is one.\n\n## Retries and limits\n\nPOST and PATCH accept an `Idempotency-Key` header (1-255 printable ASCII\ncharacters); a retry with the same key and request replays the stored\nresponse with `Idempotent-Replayed: true`.\n\nRequests are limited per owner (per workspace for workspace keys, per user\notherwise): 600 reads and 120 writes per\n60 seconds, shared with the unversioned API. A rejection is\n`429` with `Retry-After`.","version":"1.0.0-beta.4"},"servers":[{"url":"https://api.sunnysdk.com","description":"Production"},{"url":"https://api.sunnysdk.dev","description":"Staging (isolated data)"}],"components":{"schemas":{"AuditEventList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/AuditEvent"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"]},"AuditEvent":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"org_id":{"type":"string","description":"The workspace the event belongs to"},"actor":{"type":"object","properties":{"kind":{"type":"string","enum":["user","api_key","oauth_client","system"],"description":"New kinds may be added; handle values you don't recognise."},"id":{"type":"string","description":"User id (`user`), API key id (`api_key`), OAuth client id (`oauth_client`), or a job name (`system`)"},"user_id":{"description":"The person behind an OAuth client or personal API key; omitted otherwise","type":"string"}},"required":["kind","id"]},"action":{"type":"string","description":"Dotted `<target>.<verb>`","example":"quote.updated"},"target":{"type":"object","properties":{"type":{"type":"string","example":"quote"},"id":{"type":"string"}},"required":["type"]},"summary":{"type":"string"},"detail":{"description":"Action-specific detail. Free-form; keys vary by action.","type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"request_id":{"description":"`X-Request-Id` of the request that wrote the event","type":"string"}},"required":["id","created_at","org_id","actor","action","target","summary"]},"CatalogFinish":{"type":"object","properties":{"name":{"type":"string"},"color":{"type":"string","description":"Swatch color as `#rrggbb`","example":"#8a6a4f"},"material":{"description":"Id of the material Sunny's 3D viewer applies for this finish, when it has one","type":"string"},"price_adder":{"description":"Added to `list_price` when this finish is chosen; may be negative","$ref":"#/components/schemas/Money"}},"required":["name","color"],"description":"One finish a catalog item comes in. Beta.","x-sunny-stability":"beta"},"Money":{"type":"object","properties":{"amount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Integer, in minor units of `currency`"},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217 currency code","example":"USD"}},"required":["amount","currency"],"description":"An amount with its currency (C3)."},"CatalogItemList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/CatalogItem"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"],"x-sunny-stability":"beta"},"CatalogItem":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"sku":{"description":"Manufacturer SKU, when known","type":"string"},"name":{"type":"string"},"manufacturer":{"type":"string"},"family":{"description":"Product family, series or system","type":"string"},"category":{"type":"string","enum":["table","seating","storage","accent","desk","lighting"],"description":"New values may be added; handle values you don't recognise."},"list_price":{"description":"List price of the base configuration; omitted when the item is unpriced","$ref":"#/components/schemas/Money"},"model_number":{"description":"The manufacturer's model number, when it differs from the SKU. Beta.","x-sunny-stability":"beta","type":"string"},"description":{"description":"The manufacturer's product description. Beta.","x-sunny-stability":"beta","type":"string"},"retired":{"description":"Present when the item is no longer offered. It still resolves by id, so a quote line naming it can be looked up, but lists never include it. Beta.","x-sunny-stability":"beta","type":"boolean","const":true},"dimensions":{"description":"Base configuration size. In meters, whatever unit the caller displays; convert for display (1 m = 39.37 in). Beta.","$ref":"#/components/schemas/Dimensions"},"image_url":{"description":"A product image (the poster render). Beta.","x-sunny-stability":"beta","type":"string"},"finishes":{"description":"With `include=options`: the finishes it comes in, `[]` when none. Beta.","x-sunny-stability":"beta","type":"array","items":{"$ref":"#/components/schemas/CatalogFinish"}},"sizes":{"description":"With `include=options`: the sizes it comes in, `[]` when none. Beta.","x-sunny-stability":"beta","type":"array","items":{"$ref":"#/components/schemas/CatalogSize"}},"model_url":{"description":"With `include=media`: a glTF binary (`.glb`) 3D model, when there is one. Beta.","x-sunny-stability":"beta","type":"string"},"model_takes_finish_color":{"description":"With `include=media`: whether the model is tinted with the chosen finish's `color`. When false, its own materials stay as authored. Beta.","x-sunny-stability":"beta","type":"boolean"},"reference_photos":{"description":"With `include=media`: real product photos, `[]` when none. Beta.","x-sunny-stability":"beta","type":"array","items":{"$ref":"#/components/schemas/CatalogPhoto"}},"placement":{"description":"With `include=placement`. Beta.","$ref":"#/components/schemas/CatalogPlacement"},"agent_note":{"description":"Curator guidance for an AI agent placing or quoting the item. Present only when the caller holds `docs:read`. Beta.","x-sunny-stability":"beta","type":"string"},"internal_note":{"description":"Curator notes for staff. Present only for a catalog curator (a site admin or a caller holding `catalog:write`). Beta.","x-sunny-stability":"beta","type":"string"}},"required":["id","name","category","dimensions"],"description":"A catalog product. Optional sections come with `include`; notes depend on your scopes."},"Dimensions":{"type":"object","properties":{"width":{"type":"number","description":"Side to side, as placed"},"depth":{"type":"number","description":"Front to back, as placed"},"height":{"description":"Floor to top, when known","type":"number"}},"required":["width","depth"],"description":"Physical size. In meters, whatever unit the caller displays; convert for display (1 m = 39.37 in).","x-sunny-stability":"beta"},"CatalogSize":{"type":"object","properties":{"name":{"type":"string"},"dimensions":{"$ref":"#/components/schemas/Dimensions"},"price_adder":{"description":"Added to `list_price` when this size is chosen; may be negative","$ref":"#/components/schemas/Money"}},"required":["name","dimensions"],"description":"One size a catalog item comes in. Beta.","x-sunny-stability":"beta"},"CatalogPhoto":{"type":"object","properties":{"url":{"type":"string"},"scope":{"description":"`exact`: this product. `family`: a sibling in its family, close but not identical.","type":"string","enum":["exact","family"]},"source":{"description":"Where the photo came from","type":"string"},"license":{"description":"The licence the photo is used under","type":"string"}},"required":["url"],"description":"A real product photo. Beta.","x-sunny-stability":"beta"},"CatalogPlacement":{"type":"object","properties":{"shape":{"type":"string","enum":["rect","round"],"description":"Footprint outline. New values may be added; handle values you don't recognise."},"color":{"type":"string","description":"Plan-view fill color as `#rrggbb`"},"seats":{"description":"People it seats, when it seats any","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"placeholder":{"description":"A generic stand-in rather than a real product","type":"boolean","const":true},"flags":{"description":"Placement behaviours Sunny's layout tools support for this item, such as `wallSnap/mountH` or `ceilingMount`. The set grows; ignore flags you don't recognise.","type":"array","items":{"type":"string"}},"prop":{"description":"Parameters of a parametric item such as a rug","type":"object","properties":{"kind":{"type":"string"},"fields":{"type":"array","items":{"type":"string"}},"defaults":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{}},"schema":{"type":"object","propertyNames":{"type":"string"},"additionalProperties":{},"description":"JSON Schema for the parameters this prop accepts"}},"required":["kind","fields","defaults","schema"]}},"required":["shape","color"],"description":"What Sunny's layout tools need to place the item. Beta.","x-sunny-stability":"beta"},"CustomerList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Customer"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"],"x-sunny-stability":"beta"},"Customer":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"name":{"type":"string"},"email":{"type":"string"},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"updated_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"}},"required":["id","name","created_at","updated_at"],"description":"A customer a workspace quotes for."},"Deleted":{"type":"object","properties":{"id":{"type":"string","description":"The deleted object's id"},"deleted":{"type":"boolean","const":true}},"required":["id","deleted"],"description":"Confirms a delete. Beta.","x-sunny-stability":"beta"},"DiscountRule":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"priority":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Lower wins among rules that match equally specifically"},"match_type":{"type":"string","enum":["manufacturer","catalog_code","group_code","sku"],"description":"New values may be added; handle values you don't recognise."},"match_value":{"type":"string"},"buy_percents":{"type":"array","items":{"type":"number"},"description":"Left-to-right discount chain off list, e.g. `[50, 10, 10]`"},"sell_method":{"description":"How sell is derived from buy or list; omitted when the rule sets none","type":"string","enum":["markup_percent","margin_percent","sell_percent"]},"sell_value":{"description":"The percent `sell_method` applies; omitted with it","type":"number"}},"required":["id","priority","match_type","match_value","buy_percents"],"description":"One discount rule. Beta.","x-sunny-stability":"beta"},"DiscountTableList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/DiscountTable"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"],"x-sunny-stability":"beta"},"DiscountTable":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"name":{"type":"string"},"is_default":{"type":"boolean","description":"Whether new quotes bind this table's latest revision"},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"revisions":{"type":"array","items":{"$ref":"#/components/schemas/DiscountTableRevision"}}},"required":["id","name","is_default","created_at","revisions"],"description":"A workspace's dealer discount table: dealer cost, so it needs `quote_costs:read`. Beta.","x-sunny-stability":"beta"},"DiscountTableRevision":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"label":{"type":"string"},"effective_from":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"effective_to":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"rules":{"type":"array","items":{"$ref":"#/components/schemas/DiscountRule"}}},"required":["id","label","created_at","rules"],"description":"One dated revision of a discount table. Beta.","x-sunny-stability":"beta"},"Error":{"type":"object","properties":{"error":{"type":"object","properties":{"type":{"type":"string","enum":["invalid_request_error","authentication_error","permission_error","idempotency_error","rate_limit_error","api_error"],"description":"Broad error class. The documented values are the current set and may grow; handle a `type` you don't recognise, the same as an unknown `code`. Switch on `code` for specifics."},"code":{"type":"string","description":"Machine-readable error code, e.g. `parameter_invalid`, `insufficient_scope`, `rate_limited`. Additive: the set grows, and a code is never renamed or repurposed. Handle a code you don't recognise.","example":"parameter_invalid"},"message":{"type":"string","description":"Human-readable explanation. May change; don't parse it."},"param":{"description":"The request field (or header) at fault, when there is one","example":"limit","type":"string"},"request_id":{"type":"string","description":"Same value as the `X-Request-Id` response header"}},"required":["type","code","message","request_id"]}},"required":["error"]},"Manufacturer":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"slug":{"description":"Stable lookup key, accepted wherever `id` is","type":"string"},"name":{"type":"string"},"description":{"type":"string"},"website_url":{"type":"string"},"logo_url":{"type":"string"},"item_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Catalog items from this manufacturer that you can read on `/v1/catalog/items`"},"families":{"type":"array","items":{"type":"object","properties":{"name":{"type":"string","description":"As the catalog item's `family` spells it"},"item_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["name","item_count"]},"description":"Product families among those items, by name"}},"required":["id","name","item_count","families"],"description":"A manufacturer's public profile. Counts cover only the catalog items you can read. Beta.","x-sunny-stability":"beta"},"OrderDraft":{"type":"object","properties":{"format":{"type":"string","enum":["sif","ofda_xml"]},"lines":{"type":"array","items":{"type":"object","properties":{"source_line":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"sku":{"type":"string"},"description":{"type":"string"},"qty":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"list_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"List price per piece as the file states it. Integer, in minor units of the quote's `currency`."},"catalog_code":{"description":"`MC` / `Catalog/Code`","type":"string"},"manufacturer_code":{"description":"`MG` / `VendorRef`","type":"string"},"options":{"type":"array","items":{"type":"object","properties":{"code":{"type":"string"},"description":{"type":"string"},"group":{"type":"string"},"price_adder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Price adder the file states per piece. Integer, in minor units of the quote's `currency`."},"placeholder":{"type":"boolean","description":"An unselected slot the file keeps (`ON=...` / `Skipped Option`)"}},"required":["code","placeholder"]}}},"required":["source_line","sku","qty","options"]}}},"required":["format","lines"],"description":"The quote lines an order file reads as, before any catalog matching. Money is in minor units, read as the workspace currency. Beta.","x-sunny-stability":"beta"},"OrderImportLine":{"type":"object","properties":{"source_line":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"1-based line in the file where the item starts (`PN=` or `<OrderLineItem>`)"},"sku":{"type":"string","description":"The part number the file names"},"qty":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"importable":{"type":"boolean","description":"Whether the line matches exactly one quotable catalog item, so the import adds it. A line that doesn't is skipped and carries an `error` issue from the `quote` check."},"catalog_item_id":{"type":"string","description":"The matched catalog item, when importable"},"line_id":{"type":"string","description":"The quote line created from it; only on a persisted import"},"catalog_check":{"type":"string","enum":["ok","warn","fail","unverified"],"description":"The manufacturer catalog check: `unverified` when no catalog release for the part's manufacturer is loaded for your account."},"issues":{"type":"array","items":{"$ref":"#/components/schemas/OrderIssue"}}},"required":["source_line","sku","qty","importable","catalog_check","issues"],"description":"One order-file line. Beta.","x-sunny-stability":"beta"},"OrderIssue":{"type":"object","properties":{"code":{"type":"string","description":"What was found, e.g. `unknown_part`, `list_price_mismatch` (manufacturer catalog), `sku_not_found`, `list_price_differs` (quote), `missing_qty` (file). The set grows; handle codes you don't recognise by `severity`.","example":"sku_not_found"},"severity":{"type":"string","enum":["error","warning","info"]},"check":{"type":"string","enum":["file","manufacturer_catalog","quote"],"description":"Which check raised it: `file` (reading the order file), `manufacturer_catalog` (the manufacturer catalog releases loaded for your account), `quote` (matching the line to a quotable Sunny catalog item)."},"message":{"type":"string"},"option":{"description":"The option code involved, when one","type":"string"},"group":{"description":"The option group involved, when known","type":"string"},"expected":{"description":"The catalog's value, on a mismatch","type":"string"},"actual":{"description":"The file's value, on a mismatch","type":"string"},"keys":{"description":"File keys (SIF) or element paths (OFDA) involved, when any","type":"array","items":{"type":"string"}}},"required":["code","severity","check","message"],"description":"One finding about an order file. Beta.","x-sunny-stability":"beta"},"OrderImportPreflight":{"type":"object","properties":{"status":{"type":"string","enum":["pass","warn","fail","unverified"],"description":"`fail`: at least one `error` issue. `warn`: warnings only. `unverified`: no errors or warnings, but some lines had no manufacturer catalog to check against. `pass`: none of these."},"format":{"type":"string","enum":["sif","ofda_xml"]},"summary":{"type":"object","properties":{"lines":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Lines in the file"},"importable":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"skipped":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"errors":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"warnings":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"catalog_checked":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Lines checked against a manufacturer catalog"}},"required":["lines","importable","skipped","errors","warnings","catalog_checked"]},"issues":{"type":"array","items":{"$ref":"#/components/schemas/OrderIssue"},"description":"File-level findings"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/OrderImportLine"}},"catalogs":{"type":"array","items":{"type":"object","properties":{"key":{"type":"string"},"label":{"type":"string"}},"required":["key","label"]},"description":"The manufacturer catalog releases consulted"}},"required":["status","format","summary","issues","lines","catalogs"],"description":"What the import found in the file, line by line. Beta.","x-sunny-stability":"beta"},"PresentLink":{"type":"object","properties":{"url":{"type":"string","description":"The customer-facing Present page. Treat it as a secret: anyone with the URL can view the quote."},"status":{"type":"string","enum":["active","revoked"]},"quote_id":{"type":"string","description":"The quote this link presents"},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"revoked_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"}},"required":["url","status","quote_id","created_at"],"description":"A quote's Present link. One active link per quote; addressed through its quote. Beta.","x-sunny-stability":"beta"},"PresentLinkSendResult":{"type":"object","properties":{"sent":{"type":"array","items":{"type":"string"},"description":"Addresses queued for delivery"},"skipped":{"type":"array","items":{"type":"object","properties":{"email":{"type":"string"},"reason":{"type":"string","enum":["throttled"],"description":"`throttled`: already sent this link within the last 60 seconds. New reasons may be added."}},"required":["email","reason"]},"description":"Addresses not sent to this time, with the reason"}},"required":["sent","skipped"],"description":"Who a send reached. Beta.","x-sunny-stability":"beta"},"QuoteImportDryRun":{"type":"object","properties":{"draft":{"$ref":"#/components/schemas/OrderDraft"},"preflight":{"$ref":"#/components/schemas/OrderImportPreflight"}},"required":["draft","preflight"],"description":"A dry run: nothing was persisted. Beta.","x-sunny-stability":"beta"},"QuoteImportResult":{"type":"object","properties":{"quote":{"$ref":"#/components/schemas/Quote"},"preflight":{"$ref":"#/components/schemas/OrderImportPreflight"}},"required":["quote","preflight"],"description":"A persisted import. Beta.","x-sunny-stability":"beta"},"Quote":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"number":{"description":"Per-workspace document number, e.g. `Q-000123`. Read-only.","example":"Q-000123","type":"string"},"name":{"type":"string"},"status":{"type":"string","enum":["draft","presented","accepted","archived"],"description":"`accepted` is reserved and not set yet. New values may be added; handle values you don't recognise."},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217 code every money field on the quote and its lines is in. Fixed when the quote is created. Read-only.","example":"USD"},"customer_id":{"type":"string","description":"The customer this quote is for"},"discount_table_revision_id":{"type":"string","description":"The discount table revision that prices the quote's catalog lines. Present only when the caller holds `quote_costs:read`."},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"updated_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"totals":{"$ref":"#/components/schemas/QuoteTotals"}},"required":["id","name","status","currency","customer_id","created_at","updated_at","totals"],"description":"A quote. Lines are a sub-resource (`/v1/quotes/{id}/lines`), not embedded."},"QuoteTotals":{"type":"object","properties":{"list_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sum of each line's `list_unit × qty`. Integer, in minor units of the quote's `currency`."},"sell_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sum of each line's `sell_unit × qty`: what the customer pays. Integer, in minor units of the quote's `currency`."},"line_count":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Number of lines on the quote"},"buy_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sum of each line's `buy_unit × qty`: dealer cost. Present only when the caller holds `quote_costs:read`. Integer, in minor units of the quote's `currency`."},"margin":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"`sell_total - buy_total`. Present only when the caller holds `quote_costs:read`. Integer, in minor units of the quote's `currency`."},"margin_percent":{"description":"`margin / sell_total × 100`, two decimals; 0 when `sell_total` is 0. Present only when the caller holds `quote_costs:read`.","type":"number"}},"required":["list_total","sell_total","line_count"],"description":"Computed by the same calculation as the Present page, the PDF and the order SIF."},"QuoteLineList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"],"x-sunny-stability":"beta"},"QuoteLine":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"quote_id":{"type":"string","description":"The quote this line belongs to"},"kind":{"type":"string","enum":["catalog","adhoc"],"description":"`catalog`: a catalog product. `adhoc`: a typed-in charge (freight, installation) with a `description` and no catalog reference."},"position":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sort order within the quote"},"qty":{"type":"integer","minimum":1,"maximum":9007199254740991},"list_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Catalog list price per piece. Integer, in minor units of the quote's `currency`."},"sell_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Customer price per piece. Integer, in minor units of the quote's `currency`."},"catalog_item_id":{"type":"string","description":"The catalog item, on a catalog line when known"},"sku":{"description":"Manufacturer part number, on a catalog line when known","type":"string"},"description":{"description":"The printed text of an ad-hoc line","type":"string"},"options":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLineOption"},"description":"Selected options; `[]` for a single-configuration SKU. Objects may gain fields. Beta.","x-sunny-stability":"beta"},"buy_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Dealer cost per piece. Present only when the caller holds `quote_costs:read`. Integer, in minor units of the quote's `currency`."},"applied_rule_id":{"type":"string","description":"The discount rule that priced this line. Present only when the caller holds `quote_costs:read`."}},"required":["id","quote_id","kind","position","qty","list_unit","sell_unit","options"],"description":"One priced row on a quote."},"QuoteLineOption":{"type":"object","properties":{"code":{"type":"string","description":"Manufacturer option code"},"group":{"type":"string"},"description":{"type":"string"},"price_adder":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Price this option adds per piece. Integer, in minor units of the quote's `currency`."}},"required":["code"],"description":"One selected option, as exported to Order SIF. Beta.","x-sunny-stability":"beta"},"QuoteList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/Quote"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"],"x-sunny-stability":"beta"},"QuoteOrder":{"type":"object","properties":{"quote_id":{"type":"string","description":"The quote the order is built from"},"currency":{"type":"string","pattern":"^[A-Z]{3}$","description":"ISO 4217 currency code","example":"USD"},"title":{"type":"string"},"customer_name":{"type":"string"},"number":{"type":"string"},"valid_until":{"description":"As printed on the order form","type":"string"},"notes":{"type":"string"},"po_reference":{"type":"string"},"date":{"type":"string","description":"As printed on the order form"},"lines":{"type":"array","items":{"type":"object","properties":{"n":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Printed line number"},"kind":{"type":"string","enum":["catalog","adhoc"]},"part":{"description":"Part number; absent on ad-hoc lines","type":"string"},"name":{"type":"string"},"dims":{"type":"string"},"qty":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"list_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"List price per piece. Integer, in minor units of the quote's `currency`."},"sell_unit":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Customer price per piece. Integer, in minor units of the quote's `currency`."},"amount":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"`sell_unit × qty`. Integer, in minor units of the quote's `currency`."},"room":{"description":"The room the line is allotted to","type":"string"}},"required":["n","kind","name","qty","list_unit","sell_unit","amount"]}},"footnotes":{"type":"array","items":{"type":"string"},"description":"Lines the order file can't carry (ad-hoc charges have no part number)"},"total_qty":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991},"list_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sum of list. Integer, in minor units of the quote's `currency`."},"sell_total":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"Sum of sell: what the customer pays. Integer, in minor units of the quote's `currency`."},"savings":{"type":"integer","minimum":-9007199254740991,"maximum":9007199254740991,"description":"`list_total - sell_total`. Integer, in minor units of the quote's `currency`."}},"required":["quote_id","currency","title","date","lines","footnotes","total_qty","list_total","sell_total","savings"],"description":"The order a quote produces, as the order form prints it: list and customer prices, never dealer cost. Beta.","x-sunny-stability":"beta"},"QuoteVersionList":{"type":"object","properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/QuoteVersion"}},"next_cursor":{"description":"Pass as `cursor` for the next page; omitted on the last page","type":"string"}},"required":["data"],"x-sunny-stability":"beta"},"QuoteVersion":{"type":"object","properties":{"id":{"type":"string","description":"Opaque id (C1)"},"quote_id":{"type":"string","description":"The quote this version snapshots"},"number":{"type":"integer","minimum":1,"maximum":9007199254740991,"description":"1, 2, 3… per quote"},"created_at":{"type":"string","description":"ISO 8601, UTC","example":"2026-10-01T15:04:05.000Z"},"totals":{"$ref":"#/components/schemas/QuoteTotals"},"lines":{"type":"array","items":{"$ref":"#/components/schemas/QuoteLine"},"description":"The lines as presented, in the `QuoteLine` shape. Cost fields follow the same `quote_costs:read` rule as the live lines."}},"required":["id","quote_id","number","created_at","totals","lines"],"description":"A presented snapshot of a quote: what the customer saw on the Present page at that version. Beta.","x-sunny-stability":"beta"}},"securitySchemes":{"oauth2":{"type":"oauth2","description":"OAuth 2.1 authorization code + PKCE. A valid Bearer token with the operation's scope is sufficient on its own. Discovery: https://auth.sunnysdk.com/.well-known/oauth-authorization-server","flows":{"authorizationCode":{"authorizationUrl":"https://auth.sunnysdk.com/api/auth/oauth2/authorize","tokenUrl":"https://auth.sunnysdk.com/api/auth/oauth2/token","refreshUrl":"https://auth.sunnysdk.com/api/auth/oauth2/token","scopes":{"openid":"Confirm your Sunny identity.","profile":"Your display name and avatar.","email":"Your email address and whether it is verified.","offline_access":"Keep you signed in without re-approving each time.","projects:read":"Read your projects, floors, and spaces.","projects:write":"Create and update projects, floors, and spaces on your behalf.","quotes:read":"Read your quotes, customers, and discount tables.","quotes:write":"Create and update quotes, customers, and discount tables on your behalf. Discount-table edits still require workspace owner or admin. Granted only to apps Sunny has approved for it, or to workspace API keys.","quote_costs:read":"See your dealer cost and margin on quotes: buy prices, margin, and the discount rules applied. Granted only to apps Sunny has approved for it, or to workspace API keys.","orders:write":"Turn configured-order files (Order SIF or OFDA XML) into draft quotes on your behalf. Granted only to apps Sunny has approved for it, or to workspace API keys.","arrange":"Start AI room arrangements on your behalf.","workflows:run":"Start and cancel published render workflows on your behalf, spending credits for each image.","workflows:read":"List published render workflows and read the runs and images you started.","catalog:read":"Read catalog items and product data.","catalog:write":"Create and edit catalog items, typicals, photos and model assets, and see every catalog entry including dealer-only ones. Granted only to apps Sunny has approved for it, or to workspace API keys.","docs:read":"Read distilled product documentation: family composition rules, assembly guidance, and editorial notes.","embeds:write":"Mint, list, update, and revoke embeddable widgets for your catalog items.","embeds:read":"Read embeddable-widget listings and analytics for your catalog items.","webhooks:manage":"Register, update, and monitor webhook endpoints for your workspace.","org:admin":"Perform workspace owner/admin operations, such as editing discount tables. Granted only on workspace API keys.","audit:read":"See who changed what in your workspace: API keys, quotes, customers, discount tables, and webhook endpoints.","admin":"Perform Sunny staff administration across all workspaces."}}}},"apiKeyAuth":{"type":"apiKey","in":"header","name":"x-api-key","description":"API key (`sunny_sk_…` personal, minted under Account → API keys; `sunny_wsk_…` workspace, minted under Organization → API keys). Personal keys carry the non-elevated action scopes; workspace keys may also carry embeds:* and webhooks:manage. Site-admin never rides a key."}}},"tags":[{"name":"audit","x-displayName":"Audit log","description":"Workspace audit events."}],"paths":{"/v1/audit-events":{"get":{"operationId":"listAuditEvents","tags":["audit"],"summary":"List audit events","description":"Who changed what in the caller's workspace, newest first: API-key lifecycle, quote, customer, quote-line and discount-table writes, and webhook endpoint changes. Filters are exact matches. Pass `next_cursor` back as `cursor` for the next page.\n\nAllowed for a workspace API key carrying `audit:read`, and for a Bearer token or personal key carrying `audit:read` whose user is an owner or admin of their active workspace.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of audit events","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuditEventList"}}}},"400":{"description":"A query parameter is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `audit:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["audit:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string"},"description":"`next_cursor` from the previous page"},{"in":"query","name":"action","schema":{"type":"string","minLength":1},"description":"Exact match, e.g. `quote.updated`"},{"in":"query","name":"target_type","schema":{"type":"string","minLength":1}},{"in":"query","name":"target_id","schema":{"type":"string","minLength":1}},{"in":"query","name":"actor_kind","schema":{"type":"string","enum":["user","api_key","oauth_client","system"]}},{"in":"query","name":"actor_id","schema":{"type":"string","minLength":1}}]}},"/v1/catalog/items":{"get":{"operationId":"listCatalogItems","tags":["catalog"],"summary":"List catalog items","description":"The catalog items you can read, ordered by id. Dealer-only items appear only when your workspace holds an active owner or dealer relationship with their manufacturer. Filters are exact matches and combine with AND; `family` and `sku` ignore case. With `q`, results are a free-text search ordered best match first.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of catalog items","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogItemList"}}}},"400":{"description":"A query parameter is invalid (`parameter_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `catalog:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["catalog:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string","minLength":1},"description":"`next_cursor` from the previous page"},{"in":"query","name":"manufacturer","schema":{"type":"string","minLength":1,"maxLength":200},"description":"Manufacturer id or slug, as `/v1/manufacturers/{id}` takes it. An unknown one matches nothing."},{"in":"query","name":"family","schema":{"type":"string","minLength":1,"maxLength":200},"description":"Product family, case-insensitive exact match"},{"in":"query","name":"category","schema":{"type":"string","enum":["table","seating","storage","accent","desk","lighting"]}},{"in":"query","name":"sku","schema":{"type":"string","minLength":1,"maxLength":200},"description":"Manufacturer SKU, case-insensitive exact match"},{"in":"query","name":"q","schema":{"x-sunny-stability":"beta","type":"string","minLength":1,"maxLength":200},"description":"Free-text search over id, name, family, SKU and category. Every word must match. Results come best match first instead of by id. Beta."},{"in":"query","name":"include","schema":{"example":"options,media","x-sunny-stability":"beta","type":"string","pattern":"^(options|media|placement)(,(options|media|placement))*$"},"description":"Comma-separated optional sections to add to each item: `options` (finishes and sizes), `media` (3D model and product photos), `placement` (what Sunny's layout tools need). Beta."}]}},"/v1/catalog/items/{id}":{"get":{"operationId":"getCatalogItem","tags":["catalog"],"summary":"Get a catalog item","description":"One catalog item by id or slug. A retired item still resolves, so a quote line that names it can be looked up; it just no longer appears in the list.","x-sunny-stability":"beta","responses":{"200":{"description":"The catalog item","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CatalogItem"}}}},"400":{"description":"`include` names an unknown section (`parameter_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `catalog:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No catalog item you can read has this id or slug (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["catalog:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"include","schema":{"example":"options,media","x-sunny-stability":"beta","type":"string","pattern":"^(options|media|placement)(,(options|media|placement))*$"},"description":"Comma-separated optional sections to add to each item: `options` (finishes and sizes), `media` (3D model and product photos), `placement` (what Sunny's layout tools need). Beta."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/manufacturers/{id}":{"get":{"operationId":"getManufacturer","tags":["catalog"],"summary":"Get a manufacturer","description":"A manufacturer's public profile by id or slug, with counts over the catalog items you can read. List those items with `GET /v1/catalog/items?manufacturer={id}`.","x-sunny-stability":"beta","responses":{"200":{"description":"The manufacturer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Manufacturer"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `catalog:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such manufacturer (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["catalog:read"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/customers":{"get":{"operationId":"listCustomers","tags":["customers"],"summary":"List customers","description":"The workspace's customers, by name. Pass `next_cursor` back as `cursor` for the next page.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of customers","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CustomerList"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string","minLength":1},"description":"`next_cursor` from the previous page"}]},"post":{"operationId":"createCustomer","tags":["customers"],"summary":"Create a customer","description":"Adds a customer to the workspace. Send an `Idempotency-Key` to make a retry safe.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-sunny-stability":"beta","responses":{"201":{"description":"The new customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"email":{"type":"string","maxLength":200}},"required":["name"]}}}}}},"/v1/customers/{id}":{"get":{"operationId":"getCustomer","tags":["customers"],"summary":"Get a customer","x-sunny-stability":"beta","responses":{"200":{"description":"The customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such customer in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"patch":{"operationId":"updateCustomer","tags":["customers"],"summary":"Update a customer","description":"Changes the fields you send; the rest keep their values.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"x-sunny-stability":"beta","responses":{"200":{"description":"The updated customer","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Customer"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such customer in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"email":{"description":"`\"\"` clears it","type":"string","maxLength":200}}}}}}},"delete":{"operationId":"deleteCustomer","tags":["customers"],"summary":"Delete a customer","description":"Only a customer with no quotes can be deleted; otherwise `409 conflict`.","x-sunny-stability":"beta","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such customer in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The customer still has quotes (`conflict`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes/import":{"post":{"operationId":"importQuoteOrder","tags":["quotes"],"summary":"Import an order file as a draft quote","description":"Reads a configured-order file, Order SIF or OFDA XML (detected by content, never the file name), and creates a draft quote from it. Send the file as the raw body (`text/plain` or `application/xml`, options in the query string) or as JSON `{ file, customer_id, … }`.\n\nThe file is first checked against the manufacturer catalogs loaded for your account (the same check as order preflight), then each line is matched by SKU to one quotable catalog item and priced from the catalog list and the quote's discount table, like `POST /v1/quotes/{id}/lines`. A line whose SKU matches no item, or more than one, is skipped and reported in `preflight`, never a failed request. The file's options are kept on the line as given.\n\n`dry_run=true` answers `200` with the draft and the report and persists nothing. Otherwise the answer is `201` with the quote and the report, and `quote.created` is emitted once, after the lines exist. Needs `quotes:write` and `orders:write`; `discount_table_id` also needs `quote_costs:read`. Send an `Idempotency-Key` to make a retry safe.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"in":"query","name":"dry_run","schema":{"type":"string","enum":["true","false"]},"description":"`true`: read and check the file, persist nothing"},{"in":"query","name":"customer_id","schema":{"type":"string","minLength":1,"maxLength":80},"description":"For a raw (`text/plain`, `application/xml`) body: the customer to quote. Required unless `dry_run=true`. A JSON body's `customer_id` wins."},{"in":"query","name":"discount_table_id","schema":{"type":"string","minLength":1,"maxLength":80},"description":"For a raw body: the discount table to price with. Needs `quote_costs:read`."},{"in":"query","name":"name","schema":{"type":"string","minLength":1,"maxLength":120},"description":"For a raw body: quote name"}],"requestBody":{"required":true,"content":{"text/plain":{"schema":{"type":"string","description":"Order SIF or OFDA XML text"}},"application/xml":{"schema":{"type":"string","description":"OFDA XML purchase order"}},"application/json":{"schema":{"type":"object","properties":{"file":{"type":"string","minLength":1,"description":"The order file's text: Order SIF or OFDA XML, detected by content. Up to 2 MB."},"filename":{"description":"The file's name, for messages and the default quote name only","type":"string","minLength":1,"maxLength":200},"customer_id":{"description":"The customer to quote. Required unless `dry_run=true`.","type":"string","minLength":1,"maxLength":80},"discount_table_id":{"description":"The discount table to price with (its latest revision); defaults to the workspace's default table. Needs `quote_costs:read`.","type":"string","minLength":1,"maxLength":80},"name":{"description":"Quote name. Defaults to the OFDA project title or PO number, then `filename`, then \"Imported order\".","type":"string","minLength":1,"maxLength":120}},"required":["file"]}}}},"x-sunny-stability":"beta","responses":{"200":{"description":"Dry run: nothing persisted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteImportDryRun"}}}},"201":{"description":"The new draft quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteImportResult"}}}},"400":{"description":"The file isn't readable SIF or OFDA XML or holds no lines (`order_file_invalid`, `param: file`), or a field is missing or invalid (`parameter_missing`, `parameter_invalid`, `body_malformed`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` and `orders:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such customer or discount table in your workspace (`not_found`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"413":{"description":"The file is larger than 2 MB (`order_file_too_large`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write","orders:write"]},{"apiKeyAuth":[]}]}},"/v1/quotes/{id}/order":{"get":{"operationId":"getQuoteOrder","tags":["quotes"],"summary":"Get a quote's order","description":"The order the quote produces, as the order form prints it: list and customer prices, never dealer cost. Ad-hoc charges are footnoted, not ordered. `?version=n` builds from that presented version; absent, from the presented version if the quote has one, else from the live lines, so the file matches what the customer saw. The order file itself is at `order.sif` (Order SIF) and `order.xml` (OFDA XML).","x-sunny-stability":"beta","responses":{"200":{"description":"The order","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteOrder"}}}},"400":{"description":"`version` is not a positive integer (`parameter_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote or version in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"version","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"description":"A version number from `/v1/quotes/{id}/versions`. Absent: the presented version if there is one, else the live lines."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes/{id}/order.sif":{"get":{"operationId":"getQuoteOrderSif","tags":["quotes"],"summary":"Download a quote's Order SIF","description":"The quote as a classic Configura Order SIF file (`text/plain`), as an attachment. Carries part numbers, options, quantities, list and customer prices; never dealer cost. Ad-hoc charges have no part number and are left out. `?version=n` builds from that presented version; absent, from the presented version if the quote has one, else from the live lines, so the file matches what the customer saw. `POST /v1/quotes/import` reads the file back.","x-sunny-stability":"beta","responses":{"200":{"description":"Order SIF","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"`attachment; filename=\"…\"`"}},"content":{"text/plain":{"schema":{"type":"string"}}}},"400":{"description":"`version` is not a positive integer (`parameter_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote or version in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"version","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"description":"A version number from `/v1/quotes/{id}/versions`. Absent: the presented version if there is one, else the live lines."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes/{id}/order.xml":{"get":{"operationId":"getQuoteOrderXml","tags":["quotes"],"summary":"Download a quote's OFDA XML order","description":"The quote as an OFDA XML purchase order (OFDAOrder 04.04.00, `application/xml`), as an attachment. Carries part numbers, options, quantities, list and customer prices; never dealer cost. Ad-hoc charges have no part number and are left out. `?version=n` builds from that presented version; absent, from the presented version if the quote has one, else from the live lines, so the file matches what the customer saw. `POST /v1/quotes/import` reads the file back.","x-sunny-stability":"beta","responses":{"200":{"description":"OFDA XML","headers":{"Content-Disposition":{"schema":{"type":"string"},"description":"`attachment; filename=\"…\"`"}},"content":{"application/xml":{"schema":{"type":"string"}}}},"400":{"description":"`version` is not a positive integer (`parameter_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote or version in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"version","schema":{"type":"integer","minimum":1,"maximum":9007199254740991},"description":"A version number from `/v1/quotes/{id}/versions`. Absent: the presented version if there is one, else the live lines."},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes":{"get":{"operationId":"listQuotes","tags":["quotes"],"summary":"List quotes","description":"The workspace's quotes, newest first. Cost fields (`buy_unit`, `applied_rule_id`, `totals.buy_total`, `totals.margin`, `totals.margin_percent`, `discount_table_revision_id`) are present only when the caller holds `quote_costs:read`.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of quotes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteList"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string","minLength":1},"description":"`next_cursor` from the previous page"}]},"post":{"operationId":"createQuote","tags":["quotes"],"summary":"Create a quote","description":"Creates a draft quote for a customer, priced by the workspace's default discount table. The quote takes the workspace currency. Emits `quote.created`. Send an `Idempotency-Key` to make a retry safe.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}}],"x-sunny-stability":"beta","responses":{"201":{"description":"The new quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such customer in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"customer_id":{"type":"string","minLength":1,"description":"A customer in the caller's workspace"},"name":{"type":"string","minLength":1,"maxLength":120}},"required":["customer_id","name"]}}}}}},"/v1/quotes/{id}":{"get":{"operationId":"getQuote","tags":["quotes"],"summary":"Get a quote","description":"The quote and its totals; lines are at `/v1/quotes/{id}/lines`. Cost fields (`buy_unit`, `applied_rule_id`, `totals.buy_total`, `totals.margin`, `totals.margin_percent`, `discount_table_revision_id`) are present only when the caller holds `quote_costs:read`.","x-sunny-stability":"beta","responses":{"200":{"description":"The quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"patch":{"operationId":"updateQuote","tags":["quotes"],"summary":"Update a quote","description":"Renames the quote or moves its status. `draft` → `presented` snapshots a version, points the Present link at it and emits `quote.presented`; other changes emit `quote.updated`.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"x-sunny-stability":"beta","responses":{"200":{"description":"The updated quote","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Quote"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":120},"status":{"description":"`draft` → `presented` snapshots a version, points the Present link at it and emits `quote.presented`. `accepted` can't be set yet.","type":"string","enum":["draft","presented","archived"]}}}}}}},"delete":{"operationId":"deleteQuote","tags":["quotes"],"summary":"Delete a quote","description":"Deletes the quote and its lines. Emits `quote.deleted`.","x-sunny-stability":"beta","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes/{id}/lines":{"get":{"operationId":"listQuoteLines","tags":["quotes"],"summary":"List a quote's lines","description":"In quote order. Cost fields (`buy_unit`, `applied_rule_id`, `totals.buy_total`, `totals.margin`, `totals.margin_percent`, `discount_table_revision_id`) are present only when the caller holds `quote_costs:read`.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of lines","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteLineList"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string","minLength":1},"description":"`next_cursor` from the previous page"},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"post":{"operationId":"createQuoteLine","tags":["quotes"],"summary":"Add a line","description":"A catalog line names its product by `sku` (plus `manufacturer` when the SKU exists at more than one) or by `catalog_item_id`; the server prices it from the catalog list price and the quote's discount table. Change the price only through `override`; writing `override.buy_percents` needs `quote_costs:read`. An ad-hoc line (`kind: \"adhoc\"`) takes `description` and `sell_unit`, and optionally `buy_unit` (needs `quote_costs:read`). An unknown SKU is `400 sku_not_found`; an ambiguous one is `409 sku_ambiguous`. Emits `quote.updated` (`line_added`). Send an `Idempotency-Key` to make a retry safe.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"x-sunny-stability":"beta","responses":{"201":{"description":"The new line","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteLine"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `parameter_missing`), the SKU or catalog item is unknown (`sku_not_found`, `catalog_item_not_found`), or the item isn't quotable (`bad_request`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"409":{"description":"The SKU matches more than one quotable item (`sku_ambiguous`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"kind":{"description":"`catalog` (default): name the product with `sku` or `catalog_item_id`; the server prices it. `adhoc`: a typed-in charge with `description` and `sell_unit`.","type":"string","enum":["catalog","adhoc"]},"sku":{"description":"Manufacturer SKU. Add `manufacturer` when the SKU exists at more than one.","type":"string","minLength":1,"maxLength":120},"manufacturer":{"description":"Narrows `sku` to one manufacturer (case-insensitive name match)","type":"string","minLength":1,"maxLength":120},"catalog_item_id":{"type":"string","minLength":1,"maxLength":80},"qty":{"description":"Defaults to 1","type":"integer","minimum":1,"maximum":9999},"options":{"description":"Selected options, stored as given; not repriced. Beta.","x-sunny-stability":"beta","maxItems":100,"type":"array","items":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":80,"description":"Manufacturer option code"},"group":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":200},"price_adder":{"description":"Per piece, in minor units of the quote's `currency`","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["code"]}},"override":{"type":"object","properties":{"sell_unit":{"type":"integer","minimum":0,"maximum":1000000000,"description":"Customer price per piece, replacing the rule's sell method. Integer, in minor units of the quote's `currency`."},"buy_percents":{"description":"Replaces the matched rule's buy discount chain. Writing it needs `quote_costs:read`.","maxItems":10,"type":"array","items":{"type":"number","minimum":0,"maximum":100}}},"description":"The only way to change a catalog line's price. Send `{}` to drop a sell override. A caller without `quote_costs:read` can't see or change a buy override; one already on the line is kept."},"description":{"description":"Ad-hoc lines: the printed text","type":"string","minLength":1,"maxLength":200},"sell_unit":{"type":"integer","minimum":0,"maximum":1000000000,"description":"Ad-hoc lines: what the customer pays per piece. Integer, in minor units of the quote's `currency`."},"buy_unit":{"type":"integer","minimum":0,"maximum":1000000000,"description":"Ad-hoc lines: your cost per piece; defaults to `sell_unit`. Needs `quote_costs:read`. Integer, in minor units of the quote's `currency`."}}}}}}}},"/v1/quotes/{id}/lines/{lineId}":{"patch":{"operationId":"updateQuoteLine","tags":["quotes"],"summary":"Update a line","description":"Changes the fields you send and reprices the line. Catalog lines take `qty`, `options` and `override`; ad-hoc lines take `description`, `qty`, `sell_unit`, `buy_unit` and `options`. Writing `override.buy_percents` or `buy_unit` needs `quote_costs:read`; without it, a buy override already on the line is kept. Emits `quote.updated` (`line_updated`).","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"lineId","required":true}],"x-sunny-stability":"beta","responses":{"200":{"description":"The updated line","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteLine"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote, or no such line on it (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"qty":{"type":"integer","minimum":1,"maximum":9999},"options":{"description":"Replaces the selected options. Beta.","x-sunny-stability":"beta","maxItems":100,"type":"array","items":{"type":"object","properties":{"code":{"type":"string","minLength":1,"maxLength":80,"description":"Manufacturer option code"},"group":{"type":"string","maxLength":200},"description":{"type":"string","maxLength":200},"price_adder":{"description":"Per piece, in minor units of the quote's `currency`","type":"integer","minimum":-9007199254740991,"maximum":9007199254740991}},"required":["code"]}},"override":{"type":"object","properties":{"sell_unit":{"type":"integer","minimum":0,"maximum":1000000000,"description":"Customer price per piece, replacing the rule's sell method. Integer, in minor units of the quote's `currency`."},"buy_percents":{"description":"Replaces the matched rule's buy discount chain. Writing it needs `quote_costs:read`.","maxItems":10,"type":"array","items":{"type":"number","minimum":0,"maximum":100}}},"description":"The only way to change a catalog line's price. Send `{}` to drop a sell override. A caller without `quote_costs:read` can't see or change a buy override; one already on the line is kept."},"description":{"description":"Ad-hoc lines only","type":"string","minLength":1,"maxLength":200},"sell_unit":{"type":"integer","minimum":0,"maximum":1000000000,"description":"Ad-hoc lines only. Integer, in minor units of the quote's `currency`."},"buy_unit":{"type":"integer","minimum":0,"maximum":1000000000,"description":"Ad-hoc lines only. Needs `quote_costs:read`. Integer, in minor units of the quote's `currency`."}}}}}}},"delete":{"operationId":"deleteQuoteLine","tags":["quotes"],"summary":"Remove a line","description":"Emits `quote.updated` (`line_removed`).","x-sunny-stability":"beta","responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Deleted"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote, or no such line on it (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true},{"schema":{"type":"string"},"in":"path","name":"lineId","required":true}]}},"/v1/quotes/{id}/versions":{"get":{"operationId":"listQuoteVersions","tags":["quotes"],"summary":"List a quote's presented versions","description":"Every snapshot the quote was presented as, newest first, with its lines and totals. Cost fields (`buy_unit`, `applied_rule_id`, `totals.buy_total`, `totals.margin`, `totals.margin_percent`, `discount_table_revision_id`) are present only when the caller holds `quote_costs:read`. The same rule applies inside each version.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of versions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/QuoteVersionList"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string","minLength":1},"description":"`next_cursor` from the previous page"},{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes/{id}/present-link":{"get":{"operationId":"getPresentLink","tags":["present"],"summary":"Get a quote's Present link","description":"The active link, or the most recent revoked one when none is active. 404 when the quote was never presented by link.","x-sunny-stability":"beta","responses":{"200":{"description":"The Present link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresentLink"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote, or the quote has no active Present link (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:read"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]},"post":{"operationId":"createPresentLink","tags":["present"],"summary":"Mint a quote's Present link","description":"Returns the active link (200) or mints one (201). One active link per quote, so a repeat is safe. On a draft it also snapshots a version, moves the quote to `presented` and emits `quote.presented`; the link always serves that snapshot, never the moving draft.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"x-sunny-stability":"beta","responses":{"200":{"description":"The link that was already active","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresentLink"}}}},"201":{"description":"The new link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresentLink"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}]},"delete":{"operationId":"revokePresentLink","tags":["present"],"summary":"Revoke a quote's Present link","description":"The URL stops working. Comments stay on the quote. Mint again for a new URL. Emits `quote.share_revoked`.","x-sunny-stability":"beta","responses":{"200":{"description":"The revoked link","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresentLink"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote, or the quote has no active Present link (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/quotes/{id}/present-link/send":{"post":{"operationId":"sendPresentLink","tags":["present"],"summary":"Email a quote's Present link","description":"Sends the active link to up to ten addresses as the signed-in person (their name is on the email and their address is its Reply-To). Needs a person: a workspace API key gets `403 forbidden`; use an OAuth token or a personal key. An address sent this link in the last 60 seconds comes back under `skipped`. Limits: the cost-path limiter (5 calls per 60 seconds per caller) and daily send caps per person and per workspace (`429`). Emits `quote.shared`.","parameters":[{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional client-generated key (1-255 printable ASCII characters, a UUID is typical) that makes a retry safe. A repeat with the same key and the same request returns the stored response with `Idempotent-Replayed: true` instead of running again. Keys are scoped to your organization (or your user when you have none) and kept for 24 hours. See docs/api.md → Idempotency-Key.","schema":{"type":"string","minLength":1,"maxLength":255}},{"schema":{"type":"string"},"in":"path","name":"id","required":true}],"x-sunny-stability":"beta","responses":{"200":{"description":"Who the send reached","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresentLinkSendResult"}}}},"400":{"description":"A request field is invalid (`parameter_invalid`, `param` names it).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quotes:write` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such quote, or the quote has no active Present link (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quotes:write"]},{"apiKeyAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"to":{"minItems":1,"maxItems":10,"type":"array","items":{"type":"string","minLength":3,"maxLength":320},"description":"One to ten email addresses"},"message":{"description":"A short note in the email; no links","type":"string","maxLength":500}},"required":["to"]}}}}}},"/v1/discount-tables":{"get":{"operationId":"listDiscountTables","tags":["discount-tables"],"summary":"List discount tables","description":"The workspace's discount tables with their revisions and rules. Dealer cost: needs `quote_costs:read`.","x-sunny-stability":"beta","responses":{"200":{"description":"One page of discount tables","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscountTableList"}}}},"400":{"description":"A query parameter is invalid (`parameter_invalid`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quote_costs:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quote_costs:read"]},{"apiKeyAuth":[]}],"parameters":[{"in":"query","name":"limit","schema":{"type":"integer","minimum":1,"maximum":100},"description":"Page size, 1-100. Defaults to 25."},{"in":"query","name":"cursor","schema":{"type":"string","minLength":1},"description":"`next_cursor` from the previous page"}]}},"/v1/discount-tables/{id}":{"get":{"operationId":"getDiscountTable","tags":["discount-tables"],"summary":"Get a discount table","description":"One table with its revisions and rules. Needs `quote_costs:read`.","x-sunny-stability":"beta","responses":{"200":{"description":"The discount table","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DiscountTable"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `quote_costs:read` scope (`insufficient_scope`), or not allowed for this caller (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"404":{"description":"No such discount table in your workspace (`not_found`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["quote_costs:read"]},{"apiKeyAuth":[]}],"parameters":[{"schema":{"type":"string"},"in":"path","name":"id","required":true}]}},"/v1/sandbox/reset":{"post":{"operationId":"resetSandbox","tags":["sandbox"],"summary":"Reset the sandbox","description":"Deletes every quote, customer, order and discount table in the caller's sandbox organization and restores the fixture set: the default fixture discount table and the fixture customer. API keys and webhook endpoints are kept. Writes an audit event (`sandbox.reset`).\n\nOnly a sandbox test key (`sunny_wsk_test_…`) carrying `org:admin` may call this. Calling it with a live organization's key is refused (`sandbox_requires_test_key` / `test_key_requires_sandbox` for a mismatched key, `sandbox_only` otherwise).","x-sunny-stability":"beta","responses":{"200":{"description":"The sandbox was reset","content":{"application/json":{"schema":{"type":"object","properties":{"org_id":{"type":"string","description":"The sandbox organization that was reset"},"reset_at":{"type":"string","description":"When the reset finished (ISO 8601)"},"removed":{"type":"object","properties":{"quotes":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Quotes deleted, with their lines"},"customers":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Customers deleted"},"orders":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Orders deleted"},"discount_tables":{"type":"integer","minimum":0,"maximum":9007199254740991,"description":"Discount tables deleted"}},"required":["quotes","customers","orders","discount_tables"],"description":"Rows the reset removed before re-seeding the fixtures"}},"required":["org_id","reset_at","removed"],"description":"The result of a sandbox reset. Beta.","x-sunny-stability":"beta"}}}},"401":{"description":"No credentials (`credentials_missing`), or a malformed, expired, or revoked Bearer token / API key (`invalid_token`, `invalid_api_key`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"403":{"description":"Valid credentials without the `org:admin` scope (`insufficient_scope`); a live organization (`sandbox_only`); a test key on a live organization (`test_key_requires_sandbox`) or a live key on a sandbox (`sandbox_requires_test_key`); or not a workspace key (`forbidden`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limited (`rate_limited`); retry after `Retry-After` seconds.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}},"headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds to wait"}}}},"security":[{"oauth2":["org:admin"]},{"apiKeyAuth":[]}]}}}}