{"openapi":"3.0.3","info":{"title":"Wantly API v2","version":"2.0.0","description":"Catalog actions (products, categories, attributes, tags, images). POST a JSON object to \/api\/v2\/{action}. Write actions accept \"dry_run\": true to preview. Every response is {\"ok\": true, \"data\": ...} or {\"ok\": false, \"error\": {\"code\", \"message\", \"details\"}}. The same actions are available as MCP tools at POST \/api\/mcp. Read the catalog guide first (data model, rules, workflows): GET \/api\/v2\/guide."},"servers":[{"url":"https:\/\/get.wantly.eu"}],"tags":[{"name":"meta"},{"name":"guide"},{"name":"products"},{"name":"categories"},{"name":"attributes"},{"name":"tags"},{"name":"images"},{"name":"proposals"},{"name":"contacts"},{"name":"hanna"}],"paths":{"\/api\/v2\/actions":{"get":{"operationId":"listActions","summary":"Actions this API key may call, with their input schemas.","tags":["meta"],"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"responses":{"200":{"description":"List of actions"},"401":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/guide_read":{"post":{"operationId":"guide_read","summary":"Read a guide. topic \"catalog\": how products, variations, attributes, categories, tags and images work, the rules for merging and cleanup, and workflows; read it before bulk changes. topic \"proposals\": how to prepare a sales proposal (template, client, lines, groups, prices). Optionally only one section, by heading text.","tags":["guide"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"topic":{"type":"string","enum":["catalog","proposals"],"default":"catalog"},"section":{"type":"string","description":"Return only the section whose heading contains this text, e.g. \"Merge\" or \"Error codes\"."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_search":{"post":{"operationId":"products_search","summary":"Find products by text, SKU, category or flags. Returns compact rows (id, sku, name, category, price, image); use products_get for full detail.","tags":["products"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"query":{"type":"string","description":"Words that must all appear in the name or SKU."},"sku":{"type":"string","description":"Exact SKU, or a prefix ending with * (e.g. \"005417*\")."},"ids":{"type":"array","items":{"type":"integer"},"maxItems":500},"category_id":{"type":"integer","description":"Products in this category (main or extra)."},"include_subcategories":{"type":"boolean","default":true},"variable":{"type":"boolean","description":"true = only products with variations, false = only simple products."},"has_image":{"type":"boolean"},"include_deleted":{"type":"boolean","default":false},"sort":{"type":"string","enum":["id","name","sku","newest"],"default":"id"},"limit":{"type":"integer","minimum":1,"maximum":200,"default":25},"offset":{"type":"integer","minimum":0,"default":0}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_get":{"post":{"operationId":"products_get","summary":"Full detail of one product by id or SKU: descriptions, categories, attributes, tags, images, and variations with their attribute values.","tags":["products"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"sku":{"type":"string"},"include_deleted":{"type":"boolean","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_update":{"post":{"operationId":"products_update","summary":"Change a product's name, SKU, descriptions or categories. Only the fields you send are changed. With variation_sku it changes one variation instead (name, SKU, descriptions; not categories).","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"sku":{"type":"string","description":"Current SKU, to find the product."},"variation_sku":{"type":"string","description":"Change this variation instead of a product."},"name":{"type":"string","minLength":1,"maxLength":255},"new_sku":{"type":"string","minLength":1,"maxLength":191},"description":{"type":"string","description":"Short description (HTML allowed)."},"description_long":{"type":"string","description":"Long description (HTML allowed)."},"category_ids":{"type":"array","items":{"type":"integer"},"minItems":1,"description":"All categories; replaces the current ones."},"primary_category_id":{"type":"integer","description":"Main category; defaults to the first of category_ids."},"recurring":{"type":"boolean","description":"Recurring payment (subscription, e.g. a monthly module): proposal lines from this product are shown per period and kept out of the one-off total."},"unit":{"type":"string","maxLength":64,"description":"Unit, e.g. \"vnt\", \"m\u0117n\"."},"unit2":{"type":"string","maxLength":64,"description":"Unit of the second quantity (e.g. \"m\u0117n\" for users \u00d7 months)."},"vat":{"type":"number","minimum":0,"maximum":100,"description":"VAT percent of the product."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_set_price":{"post":{"operationId":"products_set_price","summary":"Set the cost and sale price of a product or one variation, for a minimum quantity (default 1). Other quantity tiers are kept unless replace_all is true. The supplier key defaults to the one already in the price.","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["sale"],"properties":{"id":{"type":"integer"},"sku":{"type":"string"},"variation_sku":{"type":"string","description":"Set the price of this variation instead of a product."},"cost":{"type":"number","minimum":0,"description":"Purchase cost; keeps the current cost of this tier when omitted."},"sale":{"type":"number","minimum":0,"description":"Sale price (without VAT, like the product editor)."},"min_quantity":{"type":"integer","minimum":1,"default":1},"delivery":{"type":"number","minimum":0},"discount_percent":{"type":"number","minimum":0,"maximum":100},"supplier":{"type":"string","minLength":1,"maxLength":100,"description":"Supplier key of the price; default = the existing one, else \"Default\"."},"replace_all":{"type":"boolean","default":false,"description":"Drop all other quantity tiers and suppliers."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_delete":{"post":{"operationId":"products_delete","summary":"Move products to the trash (soft delete, same as the Delete button). They can be brought back with products_restore.","tags":["products"],"x-capability":"catalog.delete","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["products"],"properties":{"products":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"minItems":1,"maxItems":500,"description":"Product ids (numbers) and\/or SKUs (strings)."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_restore":{"post":{"operationId":"products_restore","summary":"Bring trashed products back. Refuses a product whose SKU now belongs to a variation of an active product (i.e. it was merged).","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","items":{"type":"integer"},"minItems":1,"maxItems":500,"description":"Product ids (SKUs are ambiguous for trashed products)."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_find_same_name":{"post":{"operationId":"products_find_same_name","summary":"Groups of active simple products that share an identical name \u2014 usually variants (colour\/size only in the SKU or attributes) or duplicates. Good starting point for products_merge_preview.","tags":["products"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"query":{"type":"string","description":"Only names containing this text."},"sku_prefix":{"type":"string","description":"Only products whose SKU starts with this."},"min_group_size":{"type":"integer","minimum":2,"default":2},"limit":{"type":"integer","minimum":1,"maximum":200,"default":50},"offset":{"type":"integer","minimum":0,"default":0}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_merge_preview":{"post":{"operationId":"products_merge_preview","summary":"Before products_merge: suggested parent name and SKU, which attributes fit as variation attributes (with the value each product would get), which attributes are kept on the parent and which are dropped because they differ.","tags":["products"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["products"],"properties":{"products":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"minItems":2,"maxItems":100,"description":"Simple products to merge: ids (numbers) and\/or SKUs (strings)."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_merge":{"post":{"operationId":"products_merge","summary":"Merge simple products into one new variable product: each product becomes a variation (keeps its SKU, price and images), the originals go to the trash. Up to 3 variation attributes, e.g. Model + Color. Run products_merge_preview first and use dry_run to check.","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["attributes","variations"],"properties":{"attributes":{"type":"array","minItems":1,"maxItems":3,"items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Variation attributes by id or exact name, e.g. [\"Model\", \"Color\"]. Must be select\/colour type attributes."},"variations":{"type":"array","minItems":2,"maxItems":100,"description":"One entry per product: the product (id or SKU) and its value for each attribute, in the same order as attributes. Values are text; missing ones are created.","items":{"type":"object","required":["product","values"],"properties":{"product":{"oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"type":"string","minLength":1}}}}},"name":{"type":"string","description":"Parent name; default = suggested name. Variation values are stripped from it."},"sku":{"type":"string","description":"Parent SKU; default = suggested SKU (shared prefix or generated)."},"description":{"type":"string","description":"Parent short description; default = first product's."},"description_long":{"type":"string","description":"Parent long description; default = first product's."},"images":{"type":"string","enum":["variations","parent"],"default":"variations","description":"variations = each variation keeps its own images, the parent gallery gets the first image of each variation; parent = all images collected on the parent."},"copy_name_to_variations":{"type":"boolean","default":false},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_absorb_duplicate":{"post":{"operationId":"products_absorb_duplicate","summary":"Fold a duplicate product into the real one: copy the attributes the target does not have yet, its images (by default only when the target has none) and tags, then move the duplicate to the trash. The source or target can also be a single variation (by SKU), e.g. to give a variation the photo of a duplicate.","tags":["products"],"x-capability":"catalog.delete","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"source":{"description":"Duplicate product: id (number) or SKU (string).","oneOf":[{"type":"integer"},{"type":"string"}]},"source_variation_sku":{"type":"string","description":"Instead of a product: take images from this variation (nothing is trashed)."},"target":{"description":"Product that keeps the data: id or SKU.","oneOf":[{"type":"integer"},{"type":"string"}]},"target_variation_sku":{"type":"string","description":"Instead of a product: put images on this variation; attributes go to its parent only with attributes_to_parent."},"copy_attributes":{"type":"boolean","default":true,"description":"Copy attributes the target does not have (existing target values are never changed)."},"attributes_to_parent":{"type":"boolean","default":false,"description":"With target_variation_sku: copy attributes to the variable parent. Only when they are true for all its variations."},"copy_images":{"type":"string","enum":["if_missing","append","replace","none"],"default":"if_missing"},"copy_tags":{"type":"boolean","default":true},"trash_source":{"type":"boolean","default":true,"description":"Move the source product to the trash afterwards (ignored for source_variation_sku)."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_create":{"post":{"operationId":"products_create","summary":"Create a new product: a simple product with a price, or a variable parent with 1-3 variation attributes (e.g. FRAGRANCE) and, optionally, its variations (SKU, values, price, images). Optional shared attributes (selectable on the product) and fixed attributes (a set value shown on the product). Use products_add_variation to add variations later. Use dry_run to check first.","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["name","sku","category_ids"],"properties":{"name":{"type":"string","minLength":1,"maxLength":512},"sku":{"type":"string","minLength":1,"maxLength":128,"description":"Must not be used by any product (trashed included) or by a variation of an active product."},"description":{"type":"string","description":"Short description (HTML allowed)."},"description_long":{"type":"string","description":"Long description (HTML allowed)."},"category_ids":{"type":"array","items":{"type":"integer"},"minItems":1,"maxItems":30},"primary_category_id":{"type":"integer","description":"Primary category; default = the first of category_ids."},"cost":{"type":"number","minimum":0,"description":"Simple product only: purchase cost (default 0)."},"sale":{"type":"number","minimum":0,"description":"Simple product only: sale price (without VAT, like the product editor)."},"min_quantity":{"type":"integer","minimum":1,"default":1,"description":"Simple product only: quantity tier of the price."},"supplier":{"type":"string","minLength":1,"maxLength":100,"description":"Supplier key of the price(s); default \"Default\"."},"variation_attributes":{"type":"array","minItems":1,"maxItems":3,"items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Makes a variable product: variation attributes by id or exact name (select\/colour attributes only), e.g. [\"FRAGRANCE\"]."},"variations":{"type":"array","maxItems":200,"description":"Variable product only: the variations to create (same fields as products_add_variation).","items":{"type":"object","required":["variation_sku","values","sale"],"properties":{"variation_sku":{"type":"string","minLength":1,"maxLength":191},"name":{"type":"string","maxLength":255},"values":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"string","minLength":1},"description":"One value per variation attribute, in the order of variation_attributes."},"cost":{"type":"number","minimum":0},"sale":{"type":"number","minimum":0},"min_quantity":{"type":"integer","minimum":1,"default":1},"image_ids":{"type":"array","items":{"type":"integer"},"maxItems":30},"image_urls":{"type":"array","items":{"type":"string"},"maxItems":30}}}},"attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name.","oneOf":[{"type":"integer"},{"type":"string"}]},"value":{"type":"string","minLength":1,"description":"One value (text; for select\/colour attributes the catalog value, created if missing)."},"values":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":100,"description":"Several values (select\/colour attributes only)."}}},"description":"Shared attributes of the product (not variation attributes)."},"fixed_attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name.","oneOf":[{"type":"integer"},{"type":"string"}]},"value":{"type":"string","minLength":1,"description":"One value (text; for select\/colour attributes the catalog value, created if missing)."},"values":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":100,"description":"Several values (select\/colour attributes only)."}}},"description":"Attributes with a fixed (preset) value, e.g. Brand = La Paloma. Never also a variation attribute."},"image_ids":{"type":"array","items":{"type":"integer"},"maxItems":30,"description":"Product images (existing ids); the first is the main image."},"image_urls":{"type":"array","items":{"type":"string"},"maxItems":30,"description":"Product images to download (after image_ids). Not downloaded in a dry run."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_add_variation":{"post":{"operationId":"products_add_variation","summary":"Add one new variation to an existing variable product: its SKU, one value per variation attribute of the parent (text, in the order of the parent's variation_attributes; missing values are created), cost and sale price, and optional images. The parent gallery gets the variation's first image. Use dry_run to check first.","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["variation_sku","values","sale"],"properties":{"id":{"type":"integer","description":"Parent (variable) product id."},"sku":{"type":"string","description":"Parent (variable) product SKU."},"variation_sku":{"type":"string","minLength":1,"maxLength":191,"description":"SKU of the new variation; must not be used by any product (trashed included) or by a variation of an active product."},"name":{"type":"string","maxLength":255,"description":"Variation name; default empty (the parent name is shown)."},"values":{"type":"array","minItems":1,"maxItems":3,"items":{"type":"string","minLength":1},"description":"One value per variation attribute of the parent, in the order of its variation_attributes (see products_get), e.g. [\"NEW CAR\"]."},"cost":{"type":"number","minimum":0,"description":"Purchase cost; default 0."},"sale":{"type":"number","minimum":0,"description":"Sale price (without VAT, like the product editor)."},"min_quantity":{"type":"integer","minimum":1,"default":1},"supplier":{"type":"string","minLength":1,"maxLength":100,"description":"Supplier key of the price; default = the one used by the other variations, else \"Default\"."},"image_urls":{"type":"array","items":{"type":"string"},"maxItems":30,"description":"Images to download into the image library (reused if already imported). Not downloaded in a dry run."},"image_ids":{"type":"array","items":{"type":"integer"},"maxItems":30,"description":"Existing image ids; they come before image_urls."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_set_images":{"post":{"operationId":"products_set_images","summary":"Give a product or a single variation images from URLs (downloaded into the image library, reused if already imported) and\/or existing image ids. The first image becomes the main image.","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer","description":"Product id."},"sku":{"type":"string","description":"Product SKU."},"variation_sku":{"type":"string","description":"Set the images of this variation instead of a product."},"urls":{"type":"array","items":{"type":"string"},"maxItems":30},"image_ids":{"type":"array","items":{"type":"integer"},"maxItems":30},"mode":{"type":"string","enum":["replace","append","prepend"],"default":"replace"},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/products_tag":{"post":{"operationId":"products_tag","summary":"Add or remove a tag on products. The tag is given by id or exact name (see tags_list \/ tags_create).","tags":["products"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["tag","products"],"properties":{"tag":{"oneOf":[{"type":"integer"},{"type":"string"}]},"products":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"minItems":1,"maxItems":1000},"operation":{"type":"string","enum":["add","remove"],"default":"add"},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/categories_tree":{"post":{"operationId":"categories_tree","summary":"All product categories as a tree (or flat list with paths), with the number of active products directly in each.","tags":["categories"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"format":{"type":"string","enum":["tree","flat"],"default":"tree"},"root_id":{"type":"integer","description":"Only this category and everything under it."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/categories_create":{"post":{"operationId":"categories_create","summary":"Create a product category, at the top level or under a parent category.","tags":["categories"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"parent_id":{"type":"integer","minimum":0,"default":0,"description":"0 = top level."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/categories_update":{"post":{"operationId":"categories_update","summary":"Rename a category and\/or move it under another parent (parent_id 0 = top level). Products stay where they are.","tags":["categories"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"},"name":{"type":"string","minLength":1,"maxLength":255},"parent_id":{"type":"integer","minimum":0},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/categories_merge":{"post":{"operationId":"categories_merge","summary":"Move all products of the source categories into the target category and delete the sources (same as Merge categories in the admin). Subcategories of a source are moved under the target when move_subcategories is true, otherwise the merge is refused.","tags":["categories"],"x-capability":"catalog.delete","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["from_ids","into_id"],"properties":{"from_ids":{"type":"array","items":{"type":"integer"},"minItems":1,"maxItems":100},"into_id":{"type":"integer"},"move_subcategories":{"type":"boolean","default":false},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/categories_delete":{"post":{"operationId":"categories_delete","summary":"Delete an empty category (no subcategories, no active products). To delete one that has products, use categories_merge instead.","tags":["categories"],"x-capability":"catalog.delete","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/attributes_list":{"post":{"operationId":"attributes_list","summary":"Product attributes (features such as Color, Size, Brand) with their type and, optionally, their catalog values. Types 0 (select) and 1 (colour) can be variation attributes.","tags":["attributes"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"query":{"type":"string","description":"Part of the attribute name."},"ids":{"type":"array","items":{"type":"integer"},"maxItems":200},"variation_capable":{"type":"boolean","description":"Only attributes usable as variation attributes."},"with_values":{"type":"boolean","default":false},"values_limit":{"type":"integer","minimum":1,"maximum":1000,"default":100},"limit":{"type":"integer","minimum":1,"maximum":500,"default":100},"offset":{"type":"integer","minimum":0,"default":0}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/attributes_create":{"post":{"operationId":"attributes_create","summary":"Create a product attribute. type 0 = select value (e.g. Size, Model), 1 = colour, 2 = free text. Optionally add its first values.","tags":["attributes"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"type":{"type":"integer","enum":[0,1,2],"default":0},"multiselect":{"type":"boolean","default":false},"values":{"type":"array","items":{"type":"string","minLength":1},"maxItems":500},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/attributes_add_values":{"post":{"operationId":"attributes_add_values","summary":"Add catalog values to a select or colour attribute. Values that already exist (case-insensitive) are returned, not duplicated.","tags":["attributes"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["attribute","values"],"properties":{"attribute":{"description":"Attribute id or exact name.","oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"type":"string","minLength":1},"minItems":1,"maxItems":500},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/tags_list":{"post":{"operationId":"tags_list","summary":"Product tags (labels like \"New\" or \"Sale\") with how many active products have each.","tags":["tags"],"x-capability":"catalog.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"query":{"type":"string"}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/tags_create":{"post":{"operationId":"tags_create","summary":"Create a product tag. Assign it with products_tag.","tags":["tags"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255},"info":{"type":"string","default":""},"color":{"type":"string","default":"#405189","description":"Hex colour, e.g. #405189."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/images_import":{"post":{"operationId":"images_import","summary":"Download images from URLs into the image library and return their ids (an already imported URL returns the existing image). Attach them with products_set_images.","tags":["images"],"x-capability":"catalog.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["urls"],"properties":{"urls":{"type":"array","items":{"type":"string","minLength":8},"minItems":1,"maxItems":30},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_options":{"post":{"operationId":"proposals_options","summary":"Values to use when creating or editing proposals: statuses (ids and names), users (author \/ owner), and company defaults (currency, language, VAT, validity days), and whether the Hanna CRM integration is on.","tags":["proposals"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_templates":{"post":{"operationId":"proposals_templates","summary":"List the proposal templates of this company with their groups and line count. Before creating a proposal, show these to the user and ask which template to use (or none).","tags":["proposals"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"search":{"type":"string","maxLength":255,"description":"Text in the template name."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_search":{"post":{"operationId":"proposals_search","summary":"Find proposals (sales offers): search by name, client, code or contact person, filter by status, client, author, dates or Hanna CRM opportunity \/ client. Newest first. Returns summaries with totals and links; use proposals_get for the lines.","tags":["proposals"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"search":{"type":"string","maxLength":255,"description":"Text in the proposal name, client, client code or contact person."},"client":{"type":"string","maxLength":255,"description":"Client name contains this text."},"status":{"type":"integer","description":"Status id (proposals_options)."},"author":{"description":"Author or owner: user id or e-mail.","oneOf":[{"type":"integer"},{"type":"string"}]},"date_from":{"type":"string","description":"Proposal date from, YYYY-MM-DD."},"date_to":{"type":"string","description":"Proposal date to, YYYY-MM-DD."},"hanna_opportunity":{"type":"string","maxLength":64,"description":"Only proposals linked to this Hanna CRM opportunity uuid."},"hanna_client":{"type":"string","maxLength":64,"description":"Only proposals linked to this Hanna CRM client uuid."},"templates":{"type":"boolean","default":false,"description":"List templates instead of proposals."},"deleted":{"type":"boolean","default":false,"description":"List deleted proposals instead."},"order_by":{"type":"string","enum":["created","updated_at","nr","name","client","total"],"default":"updated_at"},"limit":{"type":"integer","minimum":1,"maximum":100,"default":20},"page":{"type":"integer","minimum":1,"default":1}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_get":{"post":{"operationId":"proposals_get","summary":"One proposal or template with all its lines (groups, products, text blocks; line ids are needed to edit or remove lines), totals as on the offer page, client, links (offer, editor, PDF \/ Excel \/ Word downloads) and view count (details: proposals_analytics). Text blocks come as a short plain preview; use full_text=true to get their HTML, and proposals_replace_text to change words inside them without losing formatting. Lines marked layout=true are column markers: never edit them.","tags":["proposals"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string","description":"Public url code or the full public \/ edit link."},"html":{"type":"boolean","default":false,"description":"Same as full_text."},"full_text":{"type":"boolean","default":false,"description":"Return text blocks and line descriptions as their complete HTML (needed before rewriting a text block). Default: a short plain-text preview."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_analytics":{"post":{"operationId":"proposals_analytics","summary":"How the client read a proposal: views (when, how many distinct visitors), reading sessions (active time, scroll depth, where they came from), which lines and sections they spent most time on, and the engagement score. Use it to decide on a follow-up.","tags":["proposals"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string","description":"Public url code or link, instead of id."},"limit":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"How many recent views \/ sessions and top lines to list."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/contacts_search":{"post":{"operationId":"contacts_search","summary":"Find client contacts (companies \/ people) by name, company code, contact person, e-mail or phone. Use the id as contact_id when creating a proposal; if the client is not found, ask the user and use contacts_create.","tags":["contacts"],"x-capability":"contacts.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"maxLength":255},"limit":{"type":"integer","minimum":1,"maximum":50,"default":10}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/contacts_get":{"post":{"operationId":"contacts_get","summary":"One client contact by id or company code: all its fields, extra fields (meta) and its latest proposals.","tags":["contacts"],"x-capability":"contacts.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"code":{"type":"string","maxLength":100,"description":"Company code, instead of id."},"proposals_limit":{"type":"integer","minimum":0,"maximum":50,"default":10,"description":"How many of the client's latest proposals to include."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/contacts_create":{"post":{"operationId":"contacts_create","summary":"Create a client contact (company or person). Search first with contacts_search: a contact with the same company code, or the same name when there is no code, is reported as a conflict with its id unless update_existing=true (then only the given fields of that contact are changed). To change a known contact use contacts_update. Use the returned id as contact_id in proposals_create.","tags":["contacts"],"x-capability":"contacts.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Company or person name."},"code":{"type":"string","maxLength":100,"description":"Company code (registration number)."},"vat":{"type":"string","maxLength":50,"description":"VAT number."},"person":{"type":"string","maxLength":255,"description":"Contact person."},"email":{"type":"string","maxLength":255},"phone":{"type":"string","maxLength":100},"address":{"type":"string","maxLength":500},"city":{"type":"string","maxLength":100},"website":{"type":"string","maxLength":255},"update_existing":{"type":"boolean","default":false,"description":"When a matching contact exists, update it instead of failing."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/contacts_update":{"post":{"operationId":"contacts_update","summary":"Change a client contact (by id from contacts_search): only the fields given are changed; an empty string clears a field. Existing proposals keep the client name they were made with.","tags":["contacts"],"x-capability":"contacts.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["id"],"properties":{"id":{"type":"integer"},"name":{"type":"string","minLength":1,"maxLength":255},"code":{"type":"string","maxLength":100,"description":"Company code; must not belong to another contact."},"vat":{"type":"string","maxLength":50},"person":{"type":"string","maxLength":255},"email":{"type":"string","maxLength":255},"phone":{"type":"string","maxLength":100},"address":{"type":"string","maxLength":500},"city":{"type":"string","maxLength":100},"website":{"type":"string","maxLength":255},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/hanna_clients_search":{"post":{"operationId":"hanna_clients_search","summary":"Find clients in Hanna CRM by company name, code or e-mail (only when the Hanna integration is on, see proposals_options). Use the uuid with hanna_opportunities_search.","tags":["hanna"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["query"],"properties":{"query":{"type":"string","minLength":1,"maxLength":255}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/hanna_opportunities_search":{"post":{"operationId":"hanna_opportunities_search","summary":"Find Hanna CRM opportunities (sales deals), newest first: of one client (uuid from hanna_clients_search) and\/or by text. Pass the opportunity uuid as hanna_opportunity to proposals_create \/ proposals_update; proposals_search lists proposals already linked to it.","tags":["hanna"],"x-capability":"proposals.read","x-mutates":false,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"client":{"type":"string","maxLength":64,"description":"Hanna client uuid."},"query":{"type":"string","maxLength":255,"description":"Text in the opportunity subject or number."}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_create":{"post":{"operationId":"proposals_create","summary":"Create a proposal (sales offer), optionally from a template (design, texts, settings and, by default, its lines), with a client and catalog or custom lines. Ask the user which template to use first (proposals_templates). Prices come from the catalog unless given. With hanna_opportunity the proposal is linked to that Hanna CRM opportunity and its name \/ client are filled from it. Returns the proposal with its lines, public link and edit link.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Proposal title. Required unless hanna_opportunity is given (then the opportunity subject is used)."},"hanna_opportunity":{"type":"string","maxLength":64,"description":"Hanna CRM opportunity uuid (hanna_opportunities_search) to link the proposal to."},"hanna_client":{"type":"string","maxLength":64,"description":"Hanna CRM client uuid; default = the opportunity's client."},"template_id":{"type":"integer","description":"Template id from proposals_templates. Any existing proposal id also works, to copy it."},"template_lines":{"type":"boolean","default":true,"description":"Copy the template lines too; false keeps only its design, texts and settings."},"items":{"type":"array","maxItems":300,"items":{"type":"object","properties":{"kind":{"type":"string","enum":["item","text","header","footer"],"default":"item","description":"item = product or service line (default); text = a text block between lines; header \/ footer = intro or closing block (e.g. greeting, reviews, FAQ). Blocks take html."},"html":{"type":"string","maxLength":200000,"description":"Content of a text \/ header \/ footer block (HTML)."},"image_ids":{"type":"array","maxItems":20,"items":{"type":"integer"},"description":"Images already in Wantly (ids, e.g. from products_get) to show on the line or block."},"image_urls":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":2000},"description":"Image URLs to download and show on the line or block (not downloaded in a dry run)."},"accepted":{"type":"boolean","description":"Mark the line as chosen by the client (when some lines are accepted, only accepted lines count in the totals)."},"sku":{"type":"string","description":"Catalog product SKU (or a variation SKU). Name, unit, VAT, image and the price for this quantity come from the catalog."},"product_id":{"type":"integer","description":"Catalog product id, instead of sku."},"variation_sku":{"type":"string","description":"Variation of a variable product. Variable products can only be added through a variation."},"name":{"type":"string","maxLength":2000,"description":"Line name. Required for a custom line that is not in the catalog; overrides the catalog name otherwise."},"quantity":{"type":"number","minimum":0,"default":1},"price":{"type":"number","description":"Unit sale price without VAT; default = catalog price for this quantity."},"cost":{"type":"number","minimum":0,"description":"Unit purchase cost; default = catalog cost."},"discount":{"type":"number","minimum":0,"maximum":100,"description":"Line discount in percent."},"vat":{"type":"number","minimum":0,"maximum":100,"description":"VAT percent; default = product VAT or the company default."},"unit":{"type":"string","maxLength":50},"description":{"type":"string","maxLength":20000,"description":"Line description shown under the name; default = product description."},"group":{"type":"string","maxLength":255,"description":"Group (section \/ tab) name. An existing group gets the line after its last line, above any footer blocks (reviews, FAQ); a new name adds a group above the footers. For an exact place use after_line_id \/ before_line_id."},"optional":{"type":"boolean","default":false,"description":"The client can choose whether to take this line."},"recurring":{"type":"boolean","description":"Recurring payment (subscription): shown per period and kept out of the one-off total. Default = the catalog product's setting."},"period":{"type":"string","maxLength":50,"description":"Period unit of a recurring line, e.g. \"m\u0117n\" (stored as the line's unit, or unit2 when the line has a second quantity)."},"quantity2":{"type":"number","minimum":0,"description":"Second quantity, e.g. 12 (months) for \"1 user \u00d7 12 m\u0117n\"; the line total is quantity \u00d7 quantity2."},"unit2":{"type":"string","maxLength":50,"description":"Unit of quantity2, e.g. \"m\u0117n\"."},"attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name (attributes_list).","oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Select \/ colour attributes: value names or ids, e.g. [\"M\",\"L\"]. Empty list removes the attribute from the line."},"text":{"type":"string","maxLength":5000,"description":"Text attributes. Empty text removes it."},"number":{"description":"Amount attributes; null removes it.","oneOf":[{"type":"number"},{"type":"null"}]}}},"description":"Attribute values shown on the line (size, colour, engraving text\u2026). Catalog lines already get the product's values; these are added on top or replace them per attribute."},"children":{"type":"array","maxItems":100,"items":{"type":"object","properties":{"kind":{"type":"string","enum":["item","text","header","footer"],"default":"item","description":"item = product or service line (default); text = a text block between lines; header \/ footer = intro or closing block (e.g. greeting, reviews, FAQ). Blocks take html."},"html":{"type":"string","maxLength":200000,"description":"Content of a text \/ header \/ footer block (HTML)."},"image_ids":{"type":"array","maxItems":20,"items":{"type":"integer"},"description":"Images already in Wantly (ids, e.g. from products_get) to show on the line or block."},"image_urls":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":2000},"description":"Image URLs to download and show on the line or block (not downloaded in a dry run)."},"accepted":{"type":"boolean","description":"Mark the line as chosen by the client (when some lines are accepted, only accepted lines count in the totals)."},"sku":{"type":"string","description":"Catalog product SKU (or a variation SKU). Name, unit, VAT, image and the price for this quantity come from the catalog."},"product_id":{"type":"integer","description":"Catalog product id, instead of sku."},"variation_sku":{"type":"string","description":"Variation of a variable product. Variable products can only be added through a variation."},"name":{"type":"string","maxLength":2000,"description":"Line name. Required for a custom line that is not in the catalog; overrides the catalog name otherwise."},"quantity":{"type":"number","minimum":0,"default":1},"price":{"type":"number","description":"Unit sale price without VAT; default = catalog price for this quantity."},"cost":{"type":"number","minimum":0,"description":"Unit purchase cost; default = catalog cost."},"discount":{"type":"number","minimum":0,"maximum":100,"description":"Line discount in percent."},"vat":{"type":"number","minimum":0,"maximum":100,"description":"VAT percent; default = product VAT or the company default."},"unit":{"type":"string","maxLength":50},"description":{"type":"string","maxLength":20000,"description":"Line description shown under the name; default = product description."},"group":{"type":"string","maxLength":255,"description":"Group (section \/ tab) name. An existing group gets the line after its last line, above any footer blocks (reviews, FAQ); a new name adds a group above the footers. For an exact place use after_line_id \/ before_line_id."},"optional":{"type":"boolean","default":false,"description":"The client can choose whether to take this line."},"recurring":{"type":"boolean","description":"Recurring payment (subscription): shown per period and kept out of the one-off total. Default = the catalog product's setting."},"period":{"type":"string","maxLength":50,"description":"Period unit of a recurring line, e.g. \"m\u0117n\" (stored as the line's unit, or unit2 when the line has a second quantity)."},"quantity2":{"type":"number","minimum":0,"description":"Second quantity, e.g. 12 (months) for \"1 user \u00d7 12 m\u0117n\"; the line total is quantity \u00d7 quantity2."},"unit2":{"type":"string","maxLength":50,"description":"Unit of quantity2, e.g. \"m\u0117n\"."},"attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name (attributes_list).","oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Select \/ colour attributes: value names or ids, e.g. [\"M\",\"L\"]. Empty list removes the attribute from the line."},"text":{"type":"string","maxLength":5000,"description":"Text attributes. Empty text removes it."},"number":{"description":"Amount attributes; null removes it.","oneOf":[{"type":"number"},{"type":"null"}]}}},"description":"Attribute values shown on the line (size, colour, engraving text\u2026). Catalog lines already get the product's values; these are added on top or replace them per attribute."}}},"description":"Sub-lines under this line (e.g. parts or included services of a package), one level deep."},"parent_line_id":{"type":"integer","description":"proposals_add_items only: add this line as a sub-line of an existing top-level line, or (for text \/ header \/ footer blocks) as a column of a layout marker (line id from proposals_get)."},"after_line_id":{"type":"integer","description":"proposals_add_items only: put the line right after this line (and its sub-lines), in its group. After a group header = first in that group."},"before_line_id":{"type":"integer","description":"proposals_add_items only: put the line right before this line."}}},"description":"Lines to add (after the template lines), in order."},"status":{"type":"integer","description":"Status id from proposals_options."},"contact_id":{"type":"integer","description":"Client contact id from contacts_search; fills client name, code and person."},"contact":{"type":"object","description":"New client contact (created, or updated when a contact with this code exists). Use contact_id for an existing one.","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Company or person name."},"code":{"type":"string","maxLength":100,"description":"Company code."},"person":{"type":"string","maxLength":255,"description":"Contact person."},"email":{"type":"string","maxLength":255},"phone":{"type":"string","maxLength":100},"address":{"type":"string","maxLength":500}}},"client":{"type":"string","maxLength":255,"description":"Client name as free text, without a contact record."},"code":{"type":"string","maxLength":100,"description":"Client company code as free text."},"person":{"type":"string","maxLength":255,"description":"Contact person as free text."},"created":{"type":"string","description":"Proposal date, YYYY-MM-DD; default today."},"deadline":{"type":"string","description":"Valid until, YYYY-MM-DD; default = date + the company validity period."},"currency":{"type":"string","maxLength":10,"description":"e.g. EUR; default = company \/ template currency."},"language":{"type":"string","maxLength":10,"description":"Language code, e.g. lt, en; default = company \/ template language."},"discount":{"type":"number","minimum":0,"maximum":100,"description":"Discount on the whole proposal, percent."},"comment":{"type":"string","maxLength":20000,"description":"Internal comment."},"author":{"description":"Author: user id or e-mail (proposals_options lists users). Default = the API caller \/ first admin.","oneOf":[{"type":"integer"},{"type":"string"}]},"owner":{"description":"Responsible manager: user id or e-mail.","oneOf":[{"type":"integer"},{"type":"string"}]},"settings":{"type":"object","description":"Display settings: showgroup (group totals), hidepronr (hide line numbers), groupinstotal, decprice, fiveround, dicper, perpos, advanceper, thediscount, selectoff.","additionalProperties":true,"properties":{}},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_update":{"post":{"operationId":"proposals_update","summary":"Change a proposal header: name, status, client, dates, currency, language, whole-proposal discount, author \/ owner, display settings. Set as_template=true to turn it into a template; hanna_opportunity links it to a Hanna CRM opportunity. Lines are changed with proposals_add_items, proposals_update_items and proposals_remove_items.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string","description":"Public url code or link, instead of id."},"name":{"type":"string","minLength":1,"maxLength":255},"as_template":{"type":"boolean","description":"true = make it a template, false = a normal proposal."},"hanna_opportunity":{"type":"string","maxLength":64,"description":"Link to (or move to) this Hanna CRM opportunity uuid; \"\" removes the link."},"hanna_client":{"type":"string","maxLength":64,"description":"Hanna CRM client uuid; default = the opportunity's client."},"status":{"type":"integer","description":"Status id from proposals_options."},"contact_id":{"type":"integer","description":"Client contact id from contacts_search; fills client name, code and person."},"contact":{"type":"object","description":"New client contact (created, or updated when a contact with this code exists). Use contact_id for an existing one.","required":["name"],"properties":{"name":{"type":"string","minLength":1,"maxLength":255,"description":"Company or person name."},"code":{"type":"string","maxLength":100,"description":"Company code."},"person":{"type":"string","maxLength":255,"description":"Contact person."},"email":{"type":"string","maxLength":255},"phone":{"type":"string","maxLength":100},"address":{"type":"string","maxLength":500}}},"client":{"type":"string","maxLength":255,"description":"Client name as free text, without a contact record."},"code":{"type":"string","maxLength":100,"description":"Client company code as free text."},"person":{"type":"string","maxLength":255,"description":"Contact person as free text."},"created":{"type":"string","description":"Proposal date, YYYY-MM-DD; default today."},"deadline":{"type":"string","description":"Valid until, YYYY-MM-DD; default = date + the company validity period."},"currency":{"type":"string","maxLength":10,"description":"e.g. EUR; default = company \/ template currency."},"language":{"type":"string","maxLength":10,"description":"Language code, e.g. lt, en; default = company \/ template language."},"discount":{"type":"number","minimum":0,"maximum":100,"description":"Discount on the whole proposal, percent."},"comment":{"type":"string","maxLength":20000,"description":"Internal comment."},"author":{"description":"Author: user id or e-mail (proposals_options lists users). Default = the API caller \/ first admin.","oneOf":[{"type":"integer"},{"type":"string"}]},"owner":{"description":"Responsible manager: user id or e-mail.","oneOf":[{"type":"integer"},{"type":"string"}]},"settings":{"type":"object","description":"Display settings: showgroup (group totals), hidepronr (hide line numbers), groupinstotal, decprice, fiveround, dicper, perpos, advanceper, thediscount, selectoff.","additionalProperties":true,"properties":{}},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_add_items":{"post":{"operationId":"proposals_add_items","summary":"Add lines to a proposal: catalog products (by SKU \/ id \/ variation SKU, priced from the catalog) or custom lines, text blocks, into a named group; with children (sub-lines) and attribute values. parent_line_id adds a sub-line under an existing line. Totals are recalculated.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["items"],"properties":{"id":{"type":"integer","description":"Proposal id."},"url":{"type":"string","description":"Public url code or link, instead of id."},"items":{"type":"array","minItems":1,"maxItems":300,"items":{"type":"object","properties":{"kind":{"type":"string","enum":["item","text","header","footer"],"default":"item","description":"item = product or service line (default); text = a text block between lines; header \/ footer = intro or closing block (e.g. greeting, reviews, FAQ). Blocks take html."},"html":{"type":"string","maxLength":200000,"description":"Content of a text \/ header \/ footer block (HTML)."},"image_ids":{"type":"array","maxItems":20,"items":{"type":"integer"},"description":"Images already in Wantly (ids, e.g. from products_get) to show on the line or block."},"image_urls":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":2000},"description":"Image URLs to download and show on the line or block (not downloaded in a dry run)."},"accepted":{"type":"boolean","description":"Mark the line as chosen by the client (when some lines are accepted, only accepted lines count in the totals)."},"sku":{"type":"string","description":"Catalog product SKU (or a variation SKU). Name, unit, VAT, image and the price for this quantity come from the catalog."},"product_id":{"type":"integer","description":"Catalog product id, instead of sku."},"variation_sku":{"type":"string","description":"Variation of a variable product. Variable products can only be added through a variation."},"name":{"type":"string","maxLength":2000,"description":"Line name. Required for a custom line that is not in the catalog; overrides the catalog name otherwise."},"quantity":{"type":"number","minimum":0,"default":1},"price":{"type":"number","description":"Unit sale price without VAT; default = catalog price for this quantity."},"cost":{"type":"number","minimum":0,"description":"Unit purchase cost; default = catalog cost."},"discount":{"type":"number","minimum":0,"maximum":100,"description":"Line discount in percent."},"vat":{"type":"number","minimum":0,"maximum":100,"description":"VAT percent; default = product VAT or the company default."},"unit":{"type":"string","maxLength":50},"description":{"type":"string","maxLength":20000,"description":"Line description shown under the name; default = product description."},"group":{"type":"string","maxLength":255,"description":"Group (section \/ tab) name. An existing group gets the line after its last line, above any footer blocks (reviews, FAQ); a new name adds a group above the footers. For an exact place use after_line_id \/ before_line_id."},"optional":{"type":"boolean","default":false,"description":"The client can choose whether to take this line."},"recurring":{"type":"boolean","description":"Recurring payment (subscription): shown per period and kept out of the one-off total. Default = the catalog product's setting."},"period":{"type":"string","maxLength":50,"description":"Period unit of a recurring line, e.g. \"m\u0117n\" (stored as the line's unit, or unit2 when the line has a second quantity)."},"quantity2":{"type":"number","minimum":0,"description":"Second quantity, e.g. 12 (months) for \"1 user \u00d7 12 m\u0117n\"; the line total is quantity \u00d7 quantity2."},"unit2":{"type":"string","maxLength":50,"description":"Unit of quantity2, e.g. \"m\u0117n\"."},"attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name (attributes_list).","oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Select \/ colour attributes: value names or ids, e.g. [\"M\",\"L\"]. Empty list removes the attribute from the line."},"text":{"type":"string","maxLength":5000,"description":"Text attributes. Empty text removes it."},"number":{"description":"Amount attributes; null removes it.","oneOf":[{"type":"number"},{"type":"null"}]}}},"description":"Attribute values shown on the line (size, colour, engraving text\u2026). Catalog lines already get the product's values; these are added on top or replace them per attribute."},"children":{"type":"array","maxItems":100,"items":{"type":"object","properties":{"kind":{"type":"string","enum":["item","text","header","footer"],"default":"item","description":"item = product or service line (default); text = a text block between lines; header \/ footer = intro or closing block (e.g. greeting, reviews, FAQ). Blocks take html."},"html":{"type":"string","maxLength":200000,"description":"Content of a text \/ header \/ footer block (HTML)."},"image_ids":{"type":"array","maxItems":20,"items":{"type":"integer"},"description":"Images already in Wantly (ids, e.g. from products_get) to show on the line or block."},"image_urls":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":2000},"description":"Image URLs to download and show on the line or block (not downloaded in a dry run)."},"accepted":{"type":"boolean","description":"Mark the line as chosen by the client (when some lines are accepted, only accepted lines count in the totals)."},"sku":{"type":"string","description":"Catalog product SKU (or a variation SKU). Name, unit, VAT, image and the price for this quantity come from the catalog."},"product_id":{"type":"integer","description":"Catalog product id, instead of sku."},"variation_sku":{"type":"string","description":"Variation of a variable product. Variable products can only be added through a variation."},"name":{"type":"string","maxLength":2000,"description":"Line name. Required for a custom line that is not in the catalog; overrides the catalog name otherwise."},"quantity":{"type":"number","minimum":0,"default":1},"price":{"type":"number","description":"Unit sale price without VAT; default = catalog price for this quantity."},"cost":{"type":"number","minimum":0,"description":"Unit purchase cost; default = catalog cost."},"discount":{"type":"number","minimum":0,"maximum":100,"description":"Line discount in percent."},"vat":{"type":"number","minimum":0,"maximum":100,"description":"VAT percent; default = product VAT or the company default."},"unit":{"type":"string","maxLength":50},"description":{"type":"string","maxLength":20000,"description":"Line description shown under the name; default = product description."},"group":{"type":"string","maxLength":255,"description":"Group (section \/ tab) name. An existing group gets the line after its last line, above any footer blocks (reviews, FAQ); a new name adds a group above the footers. For an exact place use after_line_id \/ before_line_id."},"optional":{"type":"boolean","default":false,"description":"The client can choose whether to take this line."},"recurring":{"type":"boolean","description":"Recurring payment (subscription): shown per period and kept out of the one-off total. Default = the catalog product's setting."},"period":{"type":"string","maxLength":50,"description":"Period unit of a recurring line, e.g. \"m\u0117n\" (stored as the line's unit, or unit2 when the line has a second quantity)."},"quantity2":{"type":"number","minimum":0,"description":"Second quantity, e.g. 12 (months) for \"1 user \u00d7 12 m\u0117n\"; the line total is quantity \u00d7 quantity2."},"unit2":{"type":"string","maxLength":50,"description":"Unit of quantity2, e.g. \"m\u0117n\"."},"attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name (attributes_list).","oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Select \/ colour attributes: value names or ids, e.g. [\"M\",\"L\"]. Empty list removes the attribute from the line."},"text":{"type":"string","maxLength":5000,"description":"Text attributes. Empty text removes it."},"number":{"description":"Amount attributes; null removes it.","oneOf":[{"type":"number"},{"type":"null"}]}}},"description":"Attribute values shown on the line (size, colour, engraving text\u2026). Catalog lines already get the product's values; these are added on top or replace them per attribute."}}},"description":"Sub-lines under this line (e.g. parts or included services of a package), one level deep."},"parent_line_id":{"type":"integer","description":"proposals_add_items only: add this line as a sub-line of an existing top-level line, or (for text \/ header \/ footer blocks) as a column of a layout marker (line id from proposals_get)."},"after_line_id":{"type":"integer","description":"proposals_add_items only: put the line right after this line (and its sub-lines), in its group. After a group header = first in that group."},"before_line_id":{"type":"integer","description":"proposals_add_items only: put the line right before this line."}}}},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_update_items":{"post":{"operationId":"proposals_update_items","summary":"Change existing proposal lines (also sub-lines) by line id from proposals_get: quantity, price, cost, discount, VAT, unit, name, description, optional, recurring \/ period \/ quantity2, attribute values. Group headers can be renamed with name. Totals are recalculated.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["items"],"properties":{"id":{"type":"integer","description":"Proposal id."},"url":{"type":"string","description":"Public url code or link, instead of id."},"items":{"type":"array","minItems":1,"maxItems":300,"items":{"type":"object","required":["line_id"],"properties":{"line_id":{"type":"integer"},"name":{"type":"string","minLength":1,"maxLength":100000,"description":"Line name, or for a text block its content (HTML)."},"quantity":{"type":"number","minimum":0},"price":{"type":"number"},"cost":{"type":"number","minimum":0},"discount":{"type":"number","minimum":0,"maximum":100},"vat":{"type":"number","minimum":0,"maximum":100},"unit":{"type":"string","maxLength":50},"description":{"type":"string","maxLength":20000},"optional":{"type":"boolean"},"recurring":{"type":"boolean","description":"Recurring payment (subscription), shown per period."},"accepted":{"type":"boolean","description":"Chosen by the client; when some lines are accepted only those count in the totals."},"image_ids":{"type":"array","maxItems":20,"items":{"type":"integer"},"description":"Replace the line images with these image ids ([] removes them)."},"image_urls":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":2000},"description":"Replace the line images with these downloaded images."},"period":{"type":"string","maxLength":50,"description":"Period unit, e.g. \"m\u0117n\"."},"quantity2":{"type":"number","minimum":0,"description":"Second quantity, e.g. 12 months."},"unit2":{"type":"string","maxLength":50},"replace_formatting":{"type":"boolean","default":false,"description":"Allow name to replace a formatted (HTML) text block with plain text. Prefer proposals_replace_text, or send HTML."},"attributes":{"type":"array","maxItems":50,"items":{"type":"object","required":["attribute"],"properties":{"attribute":{"description":"Attribute id or exact name (attributes_list).","oneOf":[{"type":"integer"},{"type":"string"}]},"values":{"type":"array","items":{"oneOf":[{"type":"integer"},{"type":"string"}]},"description":"Select \/ colour attributes: value names or ids, e.g. [\"M\",\"L\"]. Empty list removes the attribute from the line."},"text":{"type":"string","maxLength":5000,"description":"Text attributes. Empty text removes it."},"number":{"description":"Amount attributes; null removes it.","oneOf":[{"type":"number"},{"type":"null"}]}}},"description":"Set or remove attribute values of the line; attributes not listed are kept."}}}},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_remove_items":{"post":{"operationId":"proposals_remove_items","summary":"Remove lines from a proposal by line id (from proposals_get). Removing a group header removes all lines of that group; removing a line removes its sub-lines. Line numbers and totals are recalculated.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["line_ids"],"properties":{"id":{"type":"integer","description":"Proposal id."},"url":{"type":"string","description":"Public url code or link, instead of id."},"line_ids":{"type":"array","minItems":1,"maxItems":500,"items":{"type":"integer"}},"allow_layout":{"type":"boolean","default":false,"description":"Allow removing layout markers (column blocks with their columns). Only when the user asks."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_replace_text":{"post":{"operationId":"proposals_replace_text","summary":"Find and replace text in a proposal (e.g. the client's name \"Andriau\" \u2192 \"Skaiste\") in text blocks, headers, footers, line names and descriptions, keeping all HTML formatting, line breaks and layout. Only visible text changes, never tags. Preview with dry_run. Optionally limit to some lines.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["find","replace"],"properties":{"id":{"type":"integer","description":"Proposal id."},"url":{"type":"string","description":"Public url code or link, instead of id."},"find":{"type":"string","minLength":1,"maxLength":500},"replace":{"type":"string","maxLength":2000},"match_case":{"type":"boolean","default":true},"line_ids":{"type":"array","maxItems":500,"items":{"type":"integer"},"description":"Only these lines (default: the whole proposal)."},"include_name":{"type":"boolean","default":false,"description":"Also replace in the proposal title."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_move_items":{"post":{"operationId":"proposals_move_items","summary":"Move a proposal line (with its sub-lines) to another place: after \/ before another line, into another group or tab (to_group), under another line as a sub-line (parent_line_id), or out of its parent (parent_line_id = 0). Line numbers are recalculated.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["line_id"],"properties":{"id":{"type":"integer","description":"Proposal id."},"url":{"type":"string","description":"Public url code or link, instead of id."},"line_id":{"type":"integer","description":"The line to move (from proposals_get)."},"after_line_id":{"type":"integer","description":"Put it right after this line and its sub-lines. After a group header = first in that group."},"before_line_id":{"type":"integer","description":"Put it right before this line."},"to_group":{"type":"string","maxLength":255,"description":"Move to the end of this group \/ tab (above footer blocks)."},"parent_line_id":{"type":"integer","minimum":0,"description":"Make it a sub-line of this top-level line; 0 = make it a top-level line. Omit to keep its level."},"allow_layout":{"type":"boolean","default":false,"description":"Allow moving a layout marker with its columns (only when the user asks)."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_add_template":{"post":{"operationId":"proposals_add_template","summary":"Add another template's content to an existing proposal (like \"add as additional template\" in the editor): its lines, groups \/ tabs, text blocks, layout markers, images, sub-lines and recurring settings are copied. The template's intro (header) and closing blocks (footer: reviews, FAQ\u2026) are left out unless include_header \/ include_footer, so they are not duplicated. Place it with after_line_id \/ before_line_id or as_new_tab. Preview with dry_run; returns the new line ids.","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["template_id"],"properties":{"id":{"type":"integer","description":"Proposal to add to."},"url":{"type":"string","description":"Public url code or link, instead of id."},"template_id":{"type":"integer","description":"Template (proposals_templates) or any proposal to copy from."},"after_line_id":{"type":"integer","description":"Insert after this line and its sub-lines."},"before_line_id":{"type":"integer","description":"Insert before this line."},"as_new_tab":{"type":"boolean","default":false,"description":"Put the content in its own group \/ tab (when the template has no groups of its own; its groups are kept otherwise)."},"tab_name":{"type":"string","maxLength":255,"description":"Name of that new tab; default the template name."},"include_header":{"type":"boolean","default":false,"description":"Also copy the template's intro blocks (greeting, needs\u2026)."},"include_footer":{"type":"boolean","default":false,"description":"Also copy the template's closing blocks (reviews, FAQ, contacts)."},"include_lines":{"type":"boolean","default":true,"description":"Copy the priced lines; false = only texts and layout."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_send":{"post":{"operationId":"proposals_send","summary":"E-mail the proposal link to the client and (by default) set the status to \"Sent\". This sends a real e-mail: first call it with dry_run=true, show the user the recipient, subject and text from the preview, and send only after the user confirms. Default recipient = the client contact's e-mail; default subject = the proposal name.","tags":["proposals"],"x-capability":"proposals.send","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","properties":{"id":{"type":"integer"},"url":{"type":"string","description":"Public url code or link, instead of id."},"to":{"type":"string","maxLength":255,"description":"Recipient e-mail; default = the client contact's e-mail."},"cc":{"type":"array","maxItems":10,"items":{"type":"string","maxLength":255},"description":"Copy recipients."},"subject":{"type":"string","minLength":1,"maxLength":255,"description":"Default = the proposal name."},"message":{"type":"string","minLength":1,"maxLength":10000,"description":"Plain-text message; default = a short greeting. The proposal link is added below it unless the text already contains it."},"set_status":{"type":"boolean","default":true,"description":"Move a Pending \/ Ready proposal to \"Sent\". Later statuses are never changed."},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_delete":{"post":{"operationId":"proposals_delete","summary":"Move proposals or templates to the deleted list (soft delete; proposals_restore brings them back). Only when the user asked for it.","tags":["proposals"],"x-capability":"proposals.delete","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"integer"}},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}},"\/api\/v2\/proposals_restore":{"post":{"operationId":"proposals_restore","summary":"Bring deleted proposals or templates back (proposals_search with deleted=true lists them).","tags":["proposals"],"x-capability":"proposals.write","x-mutates":true,"security":[{"bearerAuth":[]},{"apiKeyHeader":[]}],"requestBody":{"required":true,"content":{"application\/json":{"schema":{"type":"object","required":["ids"],"properties":{"ids":{"type":"array","minItems":1,"maxItems":100,"items":{"type":"integer"}},"dry_run":{"type":"boolean","description":"Run everything and return the result, then roll back. Use it to preview a change.","default":false}}}}}},"responses":{"200":{"description":"Success","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Success"}}}},"401":{"$ref":"#\/components\/responses\/Error"},"403":{"$ref":"#\/components\/responses\/Error"},"404":{"$ref":"#\/components\/responses\/Error"},"409":{"$ref":"#\/components\/responses\/Error"},"422":{"$ref":"#\/components\/responses\/Error"}}}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","description":"API key from Settings \u2192 API (main key) or a scoped key."},"apiKeyHeader":{"type":"apiKey","in":"header","name":"X-Api-Key"}},"schemas":{"Success":{"type":"object","properties":{"ok":{"type":"boolean","enum":[true]},"action":{"type":"string"},"dry_run":{"type":"boolean"},"data":{"type":"object"}}},"Error":{"type":"object","properties":{"ok":{"type":"boolean","enum":[false]},"action":{"type":"string"},"error":{"type":"object","properties":{"code":{"type":"string","example":"not_found"},"message":{"type":"string"},"details":{"type":"object"}}}}}},"responses":{"Error":{"description":"Error","content":{"application\/json":{"schema":{"$ref":"#\/components\/schemas\/Error"}}}}}}}