FURNIDESK / DEVELOPER PORTAL

Furnidesk API · Developer Portal

Sincronizza prodotti, tessuti, immagini e scorte con il tuo sito o applicazione in modo sicuro.

API v1REST · JSONOpenAPI 3.1

https://work.furnidesk.com/api/v1

Prima connessione

L’amministratore abilita l’API aziendale. Il titolare o responsabile crea una connessione per sistema sorgente e una chiave con scadenza. Il segreto viene mostrato una sola volta: conservarlo in una variabile del server e verificare GET /me. Nessun utente del team viene creato; le operazioni sono attribuite alla connessione.

curl "https://work.furnidesk.com/api/v1/me" \
  -H "Authorization: Bearer $FURNIDESK_API_KEY"

Autenticazione e chiavi

Authorization: Bearer è obbligatorio. La sessione del pannello non autorizza l’API. I permessi effettivi combinano azienda e chiave; catalogo non implica prezzi/costi. Validità1–365 giorni, revoca disponibile. Non inserire la chiave nel browser. Disattivazione o abbonamento scaduto blocca l’accesso. Il dominio personalizzato accetta solo chiavi della sua azienda.

ScopeAccesso e comportamento
catalog:readLeggere catalogo
catalog:writeScrivere catalogo
prices:readLeggere prezzi
prices:writeScrivere prezzi
costs:readLeggere costi
costs:writeScrivere costi
media:readLeggere immagini
media:writeCaricare immagini
stock:readLeggere stock
stock:writeScrivere stock fisico

Creazione e aggiornamento prodotti

POST /products/upsert aggiorna external_id o crea. Nome e categoria obbligatori per nuovi prodotti. single è diretto, bundle raggruppa prodotti diretti con quantità. Più categorie, una collezione e un marchio. expected_revision usa revision letto con GET. Campi omessi conservati; liste inviate sostituite interamente. active:false per prodotti incompleti. DELETE archivia senza modificare documenti precedenti. V 1 non aggiorna fornitori.

{
    "external_id": "sofa-100",
    "expected_revision": 0,
    "mode": "combined",
    "name": "Solo Sofa",
    "kind": "single",
    "active": false,
    "category_ids": [
        {
            "external_id": "sofas"
        }
    ],
    "price": "1000.00",
    "currency": "EUR",
    "unit": "piece",
    "details": {
        "width": "220.00",
        "height": "85.00",
        "depth": "95.00"
    }
}

Identificativi, relazioni e lingue

external_id è univoco per azienda + connessione + risorsa. target_id associa inizialmente un record esistente. Riferimenti UUID o oggetto external_id. Categorie gerarchiche senza cicli. Nomi di marchi/collezioni condivisi; categorie e descrizioni traducibili. name/description nella lingua principale, translations nelle altre lingue attive. Mancanze usano la lingua principale.

{
    "external_id": "sofas",
    "name": "Koltuk",
    "translations": {
        "en": {
            "name": "Sofas"
        }
    }
}

Tessuti e campi di selezione

Importare tipi, gruppi e opzioni. Tessuti: pricing_scope:group e regola none/percent/fixed sul campionario, none sui campioni. Altri tipi possono usare option. fields definisce aree indipendenti, una sola scelta tra tutti i campionari permessi per area. Tessuto principale e braccioli separati. 30% × coefficient 1 =30%, ×0.3 =9%. Coefficienti espliciti richiedono prices:write.

{
    "external_id": "sofa-100",
    "expected_revision": 3,
    "fields": [
        {
            "external_id": "main-fabric",
            "name": "Ana kumaş",
            "type_id": {
                "external_id": "fabric"
            },
            "group_ids": [
                {
                    "external_id": "fabric-a"
                },
                {
                    "external_id": "fabric-b"
                }
            ],
            "coefficient": "1"
        },
        {
            "external_id": "arm-fabric",
            "name": "Kollar",
            "type_id": {
                "external_id": "fabric"
            },
            "group_ids": [
                {
                    "external_id": "fabric-a"
                }
            ],
            "coefficient": "0.3"
        }
    ]
}

Prezzi, costi e imposte

catalog aggiorna informazioni, prices solo prezzi esistenti, combined entrambi con permessi distinti. Costi: costs:read/write. Denaro come stringhe decimali nella valuta aziendale, nessuna conversione. Imposte configurate nelle impostazioni; tax_model inclusive/exclusive/none/unconfigured. Preventivi e vendite storiche mantengono i prezzi.

{
    "external_id": "sofa-100",
    "expected_revision": 4,
    "mode": "prices",
    "currency": "EUR",
    "price": "1150.00",
    "market_price": "1300.00"
}

Scorte di prodotti e tessuti

Pacchetto stock attivo, modulo abilitato e permessi stock necessari. Sedi scrivibili assegnate alla connessione. source_kind:product/option per prodotti o tessuti. /inventory/{id} usa UUID scheda magazzino; balances contiene revisioni. set/in/out modifica fisico, preserva prenotato e in arrivo, vieta fisico inferiore a prenotato o zero. Nuovo saldo revision 0, event_id univoco e quantità intere per pezzi.

{
    "event_id": "11111111-1111-4111-8111-111111111111",
    "source_kind": "product",
    "source_id": {
        "external_id": "sofa-100"
    },
    "location_id": "22222222-2222-4222-8222-222222222222",
    "action": "set",
    "quantity": "5",
    "revision": 0,
    "note": "Website initial physical balance"
}

Importazione immagini

POST /media riceve un file multipart con external_id/name. JPG/PNG/WebP fino 10 MB/40 MP; catalogo 1500 px, opzione 720 px, senza ingrandire. Trasparenza e orientamento JPEG conservati; output WebP. Dopo 202 attendere completed in /jobs/{id}, poi collegare UUID/external_id. Elaborazione sequenziale nella cartella azienda. Nuovo external_id quando cambia contenuto/nome/scopo. Nessun download URL o logo aziendale in v1.

curl -X POST "https://work.furnidesk.com/api/v1/media" \
  -H "Authorization: Bearer $FURNIDESK_API_KEY" \
  -H "Idempotency-Key: image-sofa-100-v1" \
  -F 'external_id=sofa-photo-100-v1' -F 'name=Solo Sofa Beige' \
  -F 'purpose=catalog' -F '[email protected]'

Importazione multipla e sincronizzazione

/imports:1–50 record. dry_run:true verifica senza salvare; altrimenti 202 e report created/updated/unchanged/failed. Dipendenze risolte su più passaggi, ogni record atomico. Esportare con limit 1–100/next_cursor. Salvare change_cursor e interrogare /changes; avanzare next_cursor anche per pagine filtrate vuote. origin_connection_id evita cicli. Report privati alla connessione.

{
    "dry_run": true,
    "records": [
        {
            "resource": "products",
            "body": {
                "external_id": "new-chair",
                "name": "Chair",
                "active": false,
                "category_ids": [
                    {
                        "external_id": "new-chairs"
                    }
                ]
            }
        },
        {
            "resource": "categories",
            "body": {
                "external_id": "new-chairs",
                "name": "Chairs"
            }
        }
    ]
}

Errori, limiti e tentativi

POST/DELETE richiedono Idempotency-Key 8–120 caratteri. Ripetere stessa chiave/contenuto per errori temporanei. 409 richiede rilettura e risoluzione del conflitto. 401 accesso,403 permessi,404 assente,422 validazione,429 limite,503 occupato.120 richieste/chiave/minuto, limite azienda 5 volte; JSON 1 MB,20 processi aperti,100 MB immagini per connessione. Errori autorizzazione storage sospendono immagini fino alla riparazione operatore. /jobs/{id}/retry riprende processi falliti/sospesi con chiave attiva della stessa connessione.

Invia un User-Agent con nome e versione dell’applicazione, per esempio FurnideskConnector/1.0. Una risposta 403 non JSON o HTML può provenire dalla protezione di rete prima della verifica API. Controlla il Content-Type e contatta l’assistenza.

{
    "error": {
        "code": "revision_conflict",
        "message": "Read the current revision before updating.",
        "details": {
            "current_revision": 5
        }
    },
    "request_id": "33333333-3333-4333-8333-333333333333"
}

Versioni e roadmap

V 1 comprende catalogo, opzioni, immagini, prezzi/costi e stock facoltativo. Ordini, clienti e conti sono fasi future senza endpoint attivi. Webhook e connettori pianificati. Estensioni compatibili in v1 senza nuovi permessi automatici; modifiche incompatibili in nuova versione maggiore. Il portale statico non interroga il database.

Riferimento endpoint

I nomi tecnici sono identici in tutte le lingue. OpenAPI descrive campi, vincoli, filtri, esempi ed errori.

HTTPEndpointAccesso e comportamento
GET/api/v1/productscatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/products/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
DELETE/api/v1/products/{id}catalog:write. Soft archive; existing quotations and sales remain unchanged.
POST/api/v1/products/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/categoriescatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/categories/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
POST/api/v1/categories/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/collectionscatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/collections/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
POST/api/v1/collections/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/brandscatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/brands/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
POST/api/v1/brands/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/customization-typescatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/customization-types/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
POST/api/v1/customization-types/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/customization-groupscatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/customization-groups/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
POST/api/v1/customization-groups/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/customization-optionscatalog:read; prices mode requires prices:read. prices:read and costs:read independently expose pricing and cost fields.
GET/api/v1/customization-options/{id}catalog:read. Pricing and costs are included only with their separate read scopes. For price-only keys, use the list endpoint with mode=prices.
POST/api/v1/customization-options/upsertcatalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection.
GET/api/v1/mediamedia:read
POST/api/v1/mediamedia:write. One file per request; serial worker. JPG/PNG/WebP <=10 MB /40 MP; catalog max1500px, option max720px, no upscaling. Immutable image versions: changing content/name/purpose requires a new external_id.
GET/api/v1/media/{id}media:read
GET/api/v1/meAny valid key.
GET/api/v1/changesResource read scope required. origin_connection_id supports loop prevention. Always persist next_cursor, even for an empty filtered page.
POST/api/v1/importsWrite scopes for every record.
GET/api/v1/importsAny valid key; jobs from another connection remain hidden.
GET/api/v1/jobsAny valid key; jobs from another connection remain hidden.
GET/api/v1/jobs/{id}Any valid key on the same connection. Per-record failures appear in report counts.
GET/api/v1/imports/{id}Any valid key on the same connection. Per-record failures appear in report counts.
POST/api/v1/jobs/{id}/retryRequired write scopes; a replacement active key must belong to the same connection. Storage authorization failures also require an operator to resume storage after fixing credentials.
POST/api/v1/imports/{id}/retryRequired write scopes; a replacement active key must belong to the same connection. Storage authorization failures also require an operator to resume storage after fixing credentials.
GET/api/v1/inventorystock:read; stock package and module must be active. Detail balances contain revisions. Pagination cursor is inventory item UUID.
POST/api/v1/inventorystock:write; company must have active stock package and enabled module. Location must be assigned to connection.
GET/api/v1/inventory/{id}stock:read; id is inventory item UUID, not product UUID.

Modelli di dati

I nomi tecnici sono identici in tutte le lingue. OpenAPI descrive campi, vincoli, filtri, esempi ed errori.

Reference
{
    "oneOf": [
        {
            "type": "string",
            "format": "uuid"
        },
        {
            "type": "object",
            "properties": {
                "external_id": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 200
                }
            },
            "required": [
                "external_id"
            ],
            "additionalProperties": false,
            "description": ""
        }
    ],
    "description": "Furnidesk UUID or external ID in the current company + connection + resource namespace."
}
SelectionField
{
    "type": "object",
    "properties": {
        "id": {
            "type": "string",
            "format": "uuid"
        },
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "name": {
            "type": "string"
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "type_id": {
            "$ref": "#/components/schemas/Reference"
        },
        "group_ids": {
            "type": "array",
            "items": {
                "$ref": "#/components/schemas/Reference"
            },
            "maxItems": 100
        },
        "coefficient": {
            "type": "string",
            "pattern": "^\\d+(\\.\\d{1,4})?$",
            "description": "0 < coefficient <= 100. Explicit coefficients require prices:write. 1 × 30% = 30%; 0.3 × 30% = 9%."
        }
    },
    "required": [
        "name",
        "type_id"
    ],
    "additionalProperties": false,
    "description": "Supply id or external_id. Each field permits one option across its groups; main fabric and arms are separate fields."
}
Error
{
    "type": "object",
    "required": [
        "error",
        "request_id"
    ],
    "properties": {
        "request_id": {
            "type": "string",
            "format": "uuid"
        },
        "error": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "string"
                },
                "message": {
                    "type": "string"
                },
                "details": {
                    "type": [
                        "object",
                        "array"
                    ]
                },
                "retry_after": {
                    "type": "integer"
                }
            }
        }
    }
}
Envelope
{
    "type": "object",
    "description": "Record fields are filtered by read scopes. Decimal values are strings. All responses include request_id; successful writes can include replayed:true.",
    "properties": {
        "request_id": {
            "type": "string",
            "format": "uuid"
        },
        "outcome": {
            "enum": [
                "created",
                "updated",
                "unchanged"
            ]
        },
        "record": {
            "type": "object"
        },
        "records": {
            "type": "array",
            "items": {
                "type": "object"
            }
        },
        "job": {
            "type": "object"
        },
        "principal": {
            "type": "object"
        },
        "report": {
            "type": "object"
        },
        "next_cursor": {
            "type": [
                "string",
                "null"
            ]
        },
        "has_more": {
            "type": "boolean"
        },
        "replayed": {
            "type": "boolean"
        }
    }
}
products-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string",
            "maxLength": 200
        },
        "sku": {
            "type": "string",
            "maxLength": 100
        },
        "kind": {
            "enum": [
                "single",
                "bundle"
            ]
        },
        "active": {
            "type": "boolean",
            "default": false
        },
        "category_ids": {
            "type": "array",
            "items": {
                "$ref": "#/components/schemas/Reference"
            },
            "minItems": 1,
            "maxItems": 50
        },
        "collection_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        },
        "brand_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "details": {
            "type": "object",
            "properties": {
                "description": {
                    "type": "string",
                    "maxLength": 4000
                },
                "width": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "Non-negative decimal; cm for width/height/depth, kg for weight, m³ for packaged volume."
                },
                "height": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "Non-negative decimal; cm for width/height/depth, kg for weight, m³ for packaged volume."
                },
                "depth": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "Non-negative decimal; cm for width/height/depth, kg for weight, m³ for packaged volume."
                },
                "weight": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "Non-negative decimal; cm for width/height/depth, kg for weight, m³ for packaged volume."
                },
                "volume": {
                    "type": [
                        "string",
                        "null"
                    ],
                    "description": "Non-negative decimal; cm for width/height/depth, kg for weight, m³ for packaged volume."
                }
            },
            "required": [],
            "additionalProperties": false,
            "description": ""
        },
        "unit": {
            "enum": [
                "piece",
                "metre",
                "sqm",
                "cbm"
            ]
        },
        "components": {
            "type": "array",
            "items": {
                "type": "object",
                "properties": {
                    "product_id": {
                        "$ref": "#/components/schemas/Reference"
                    },
                    "quantity": {
                        "type": "integer",
                        "minimum": 1,
                        "maximum": 10000
                    }
                },
                "required": [
                    "product_id",
                    "quantity"
                ],
                "additionalProperties": false,
                "description": ""
            },
            "maxItems": 100
        },
        "media_ids": {
            "type": "array",
            "items": {
                "$ref": "#/components/schemas/Reference"
            },
            "maxItems": 50
        },
        "cover_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        },
        "fields": {
            "type": "array",
            "items": {
                "$ref": "#/components/schemas/SelectionField"
            },
            "maxItems": 100
        },
        "price": {
            "type": "string",
            "pattern": "^\\d{1,16}(\\.\\d{1,2})?$",
            "description": "Non-negative decimal string. Money uses the company currency; no currency conversion."
        },
        "market_price": {
            "oneOf": [
                {
                    "type": "string",
                    "pattern": "^\\d{1,16}(\\.\\d{1,2})?$",
                    "description": "Non-negative decimal string. Money uses the company currency; no currency conversion."
                },
                {
                    "type": "null"
                }
            ]
        },
        "cost": {
            "oneOf": [
                {
                    "type": "string",
                    "pattern": "^\\d{1,16}(\\.\\d{1,2})?$",
                    "description": "Non-negative decimal string. Money uses the company currency; no currency conversion."
                },
                {
                    "type": "null"
                }
            ]
        },
        "tax_rate": {
            "oneOf": [
                {
                    "type": "string",
                    "pattern": "^\\d{1,16}(\\.\\d{1,2})?$",
                    "description": "Non-negative decimal string. Money uses the company currency; no currency conversion."
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
categories-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string"
        },
        "parent_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "description": {
            "type": "string"
        },
        "image_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
collections-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string"
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "description": {
            "type": "string"
        },
        "image_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
brands-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string"
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "description": {
            "type": "string"
        },
        "image_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
customization-types-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string"
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "pricing_scope": {
            "enum": [
                "group",
                "option"
            ]
        },
        "archived": {
            "type": "boolean"
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
customization-groups-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string"
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "type_id": {
            "$ref": "#/components/schemas/Reference"
        },
        "active": {
            "type": "boolean"
        },
        "photo_enabled": {
            "type": "boolean"
        },
        "pricing_mode": {
            "enum": [
                "none",
                "percent",
                "fixed"
            ]
        },
        "pricing_value": {
            "type": "string",
            "pattern": "^\\d{1,16}(\\.\\d{1,2})?$",
            "description": "Non-negative decimal string. Money uses the company currency; no currency conversion."
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
customization-options-upsert
{
    "type": "object",
    "properties": {
        "external_id": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200
        },
        "target_id": {
            "type": "string",
            "format": "uuid"
        },
        "expected_revision": {
            "type": "integer",
            "minimum": 0,
            "description": "Required for updates; read record.revision first. Use 0 or omit for a new record."
        },
        "mode": {
            "enum": [
                "catalog",
                "prices",
                "combined"
            ],
            "default": "catalog"
        },
        "currency": {
            "type": "string",
            "description": "Must equal company currency when provided."
        },
        "name": {
            "type": "string"
        },
        "translations": {
            "type": "object",
            "description": "Enabled language codes only; main name/description fields use the company base language. Category names are translated; collection/brand names are shared. Omitted fields are preserved.",
            "additionalProperties": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            }
        },
        "group_id": {
            "$ref": "#/components/schemas/Reference"
        },
        "code": {
            "type": "string"
        },
        "active": {
            "type": "boolean"
        },
        "media_id": {
            "oneOf": [
                {
                    "$ref": "#/components/schemas/Reference"
                },
                {
                    "type": "null"
                }
            ]
        },
        "pricing_mode": {
            "enum": [
                "none",
                "percent",
                "fixed"
            ]
        },
        "pricing_value": {
            "type": "string",
            "pattern": "^\\d{1,16}(\\.\\d{1,2})?$",
            "description": "Non-negative decimal string. Money uses the company currency; no currency conversion."
        }
    },
    "required": [
        "external_id"
    ],
    "additionalProperties": false,
    "description": "Partial update. New records also require name and relevant category/type/group references. catalog mode excludes price/cost; prices mode changes only price fields on existing records; combined allows both with separate scopes."
}
StockWrite
{
    "type": "object",
    "properties": {
        "event_id": {
            "type": "string",
            "format": "uuid"
        },
        "source_kind": {
            "enum": [
                "product",
                "option"
            ]
        },
        "source_id": {
            "$ref": "#/components/schemas/Reference"
        },
        "location_id": {
            "type": "string",
            "format": "uuid"
        },
        "action": {
            "enum": [
                "set",
                "in",
                "out"
            ]
        },
        "quantity": {
            "type": "string",
            "pattern": "^\\d{1,9}(\\.\\d{1,3})?$"
        },
        "revision": {
            "type": "integer",
            "minimum": 0
        },
        "note": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000
        }
    },
    "required": [
        "event_id",
        "source_kind",
        "source_id",
        "location_id",
        "action",
        "quantity",
        "revision",
        "note"
    ],
    "additionalProperties": false,
    "description": "Updates physical stock only in an assigned location. Preserves reserved and incoming stock. Cannot reduce physical below reserved or zero. Pieces must be whole numbers. A new balance uses revision 0. Read inventory/{item_id} balances for subsequent revisions."
}
Import
{
    "type": "object",
    "properties": {
        "dry_run": {
            "type": "boolean",
            "default": false
        },
        "records": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
                "oneOf": [
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "products"
                            },
                            "body": {
                                "$ref": "#/components/schemas/products-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    },
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "categories"
                            },
                            "body": {
                                "$ref": "#/components/schemas/categories-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    },
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "collections"
                            },
                            "body": {
                                "$ref": "#/components/schemas/collections-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    },
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "brands"
                            },
                            "body": {
                                "$ref": "#/components/schemas/brands-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    },
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "customization-types"
                            },
                            "body": {
                                "$ref": "#/components/schemas/customization-types-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    },
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "customization-groups"
                            },
                            "body": {
                                "$ref": "#/components/schemas/customization-groups-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    },
                    {
                        "type": "object",
                        "properties": {
                            "resource": {
                                "const": "customization-options"
                            },
                            "body": {
                                "$ref": "#/components/schemas/customization-options-upsert"
                            }
                        },
                        "required": [
                            "resource",
                            "body"
                        ],
                        "additionalProperties": false,
                        "description": ""
                    }
                ]
            }
        }
    },
    "required": [
        "records"
    ],
    "additionalProperties": false,
    "description": "Dry run returns a synchronous report and rolls back all changes. Otherwise 202 creates a job. Records resolve dependencies in multiple passes; each record is atomic; failures do not cancel other records."
}