FURNIDESK / DEVELOPER PORTAL
Furnidesk API · Developer Portal
Synchronisieren Sie Produkte, Stoffoptionen, Bilder und Lagerbestände sicher mit Ihrer Website oder Anwendung.
https://work.furnidesk.com/api/v1
Erste Verbindung
Der Plattformadministrator aktiviert den API-Zugang Ihrer Firma. Eigentümer oder Manager erstellen unter API-Integrationen je Quellsystem eine Verbindung und einen zeitlich begrenzten Schlüssel. Das Geheimnis wird einmal angezeigt. Speichern Sie es als serverseitige Umgebungsvariable und prüfen Sie GET /me. API-Aktionen werden der Verbindung zugeordnet; kein Teamkonto wird erzeugt.
curl "https://work.furnidesk.com/api/v1/me" \
-H "Authorization: Bearer $FURNIDESK_API_KEY"Authentifizierung und Schlüssel
Authorization: Bearer ist für jede Anfrage erforderlich. Panel-Sitzungen ersetzen keinen API-Schlüssel. Die effektiven Rechte ergeben sich aus Firmengenehmigung und Schlüssel. Katalogrechte enthalten keine Preis- oder Kostenrechte. Schlüssel gelten 1–365 Tage und können widerrufen werden. Niemals im Browsercode speichern. Deaktivierung oder abgelaufenes Abonnement stoppt den Zugriff. Eine eigene Domain akzeptiert nur Schlüssel ihrer Firma.
| Scope | Zugriff und Verhalten |
|---|---|
catalog:read | Katalog lesen |
catalog:write | Katalog schreiben |
prices:read | Preise lesen |
prices:write | Preise schreiben |
costs:read | Kosten lesen |
costs:write | Kosten schreiben |
media:read | Bilder lesen |
media:write | Bilder laden |
stock:read | Bestand lesen |
stock:write | Physischen Bestand schreiben |
Produkte anlegen und aktualisieren
POST /products/upsert aktualisiert anhand external_id oder legt neu an. Neue Produkte benötigen Namen und Kategorie. single bezeichnet ein direktes Produkt, bundle einen Satz direkter Produkte mit Mengen. Mehrere Kategorien, eine Kollektion und eine Marke sind möglich. expected_revision muss der gelesenen revision entsprechen. Fehlende Felder bleiben erhalten; gesendete Listen ersetzen die gesamte Liste. Unvollständige Produkte mit active:false speichern. DELETE archiviert; historische Dokumente bleiben erhalten. Lieferanten werden in v1 nicht geschrieben.
{
"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"
}
}Identitäten, Beziehungen und Sprachen
external_id gilt innerhalb Firma + Verbindung + Ressource. target_id kann einen bestehenden Datensatz erstmalig zuordnen. Referenzen sind UUIDs oder external_id-Objekte. Kategorien dürfen Eltern, aber keine Zyklen haben. Marken- und Kollektionsnamen sind gemeinsam; Kategorienamen und Beschreibungen sind übersetzbar. name/description enthalten die Hauptsprache, translations die weiteren aktiven Sprachen. Fehlende Übersetzungen verwenden die Hauptsprache.
{
"external_id": "sofas",
"name": "Koltuk",
"translations": {
"en": {
"name": "Sofas"
}
}
}Stoffe und Auswahlfelder
Zuerst customization-types, dann groups und options importieren. Stofftypen verwenden pricing_scope:group; die Stoffkarte trägt percent/fixed/none, Stoffproben none. Andere Typen können option verwenden. Produkt-fields definieren unabhängige Bereiche mit genau einer Auswahl je Bereich über alle erlaubten Karten. Hauptstoff und Arme sind getrennt. 30% × coefficient 1 ergibt 30%, ×0.3 ergibt 9%. Explizite Koeffizienten erfordern 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"
}
]
}Preise, Kosten und Steuern
catalog ändert Produktinformationen, prices nur Preise bestehender Zuordnungen, combined beides mit getrennten Rechten. Kosten erfordern costs:read/write. Geld als Dezimaltext in Firmenwährung senden; keine Währungsumrechnung. Steuersätze müssen in den Firmeneinstellungen bestehen. tax_model ist inclusive/exclusive/none/unconfigured. Alte Angebots- und Verkaufsbelege behalten ihre Preise.
{
"external_id": "sofa-100",
"expected_revision": 4,
"mode": "prices",
"currency": "EUR",
"price": "1150.00",
"market_price": "1300.00"
}Produkt- und Stoffbestand
Lagerzugriff benötigt aktives Lagerpaket, aktiviertes Modul und stock-Rechte. Schreiborte werden der Verbindung zugewiesen. source_kind:product/option verbindet Produkte oder Stoffe. /inventory/{id} verwendet die Lagerkarten-UUID; balances enthält Revisionen. set/in/out verändert physische Menge, erhält Reservierungen und Eingangsmengen und verbietet Bestände unter reserviert oder null. Neue Bilanz: revision 0; eindeutige event_id und ganze Stückzahlen.
{
"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"
}Bilder importieren
POST /media lädt ein Bild per multipart mit external_id und name. JPG/PNG/WebP bis 10 MB/40 MP; Katalog maximal 1500 px, Optionen 720 px; kein Vergrößern. Transparenz und JPEG-Ausrichtung bleiben erhalten. 202 erzeugt einen Job; nach completed das Bild über UUID/external_id verknüpfen. Verarbeitung erfolgt einzeln im Firmenordner als WebP. Änderungen am Bild/Namen/Zweck benötigen eine neue external_id. Keine URL-Downloads oder Firmenlogos 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]'Stapelimport und Synchronisierung
/imports nimmt 1–50 Datensätze. dry_run:true prüft ohne Speicherung. Sonst 202, Bericht über /jobs/{id} mit created/updated/unchanged/failed. Abhängigkeiten werden in mehreren Durchläufen gelöst; jeder Datensatz ist atomar. GET-Listen mit limit 1–100 und next_cursor vollständig lesen. change_cursor speichern und /changes abfragen. Auch leere gefilterte Seiten aktualisieren next_cursor. origin_connection_id verhindert Schleifen. Aufträge sind nur innerhalb derselben Verbindung sichtbar.
{
"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"
}
}
]
}Fehler, Grenzen und Wiederholungen
POST/DELETE benötigen Idempotency-Key 8–120 Zeichen. Gleichen Schlüssel nur mit identischem Inhalt wiederholen. 409: Revision oder Konflikt; zuerst erneut lesen. 401 Zugang,403 Rechte,404 fehlt,422 Validierung,429 Limit,503 vorübergehend beschäftigt. Standard 120 Anfragen/Schlüssel/Minute, Firmenlimit 5-fach. JSON 1 MB,20 offene Jobs und 100 MB Bildwarteschlange je Verbindung. Speicherrechtefehler pausieren Bilder bis zur Reparatur durch den Betreiber. Fehlgeschlagene/pausierte Jobs: POST /jobs/{id}/retry mit aktivem Schlüssel derselben Verbindung.
Senden Sie einen User-Agent mit Anwendungsname und Version, etwa FurnideskConnector/1.0. Eine 403-Antwort ohne JSON oder eine HTML-Antwort kann vor der API-Prüfung von der Netzwerksicherheit stammen. Prüfen Sie den Content-Type und wenden Sie sich an den Support.
{
"error": {
"code": "revision_conflict",
"message": "Read the current revision before updating.",
"details": {
"current_revision": 5
}
},
"request_id": "33333333-3333-4333-8333-333333333333"
}Versionen und Roadmap
V 1 integriert Katalog, Eigenschaften, Bilder, Preise/Kosten und optional Lager. Aufträge, Kunden und Konten folgen später und besitzen keine aktiven Endpoints. Webhooks und fertige Konnektoren sind geplant. Neue kompatible Ressourcen erweitern v1; neue Rechte werden bestehenden Schlüsseln nicht automatisch gegeben. Inkompatible Änderungen erhalten eine neue Hauptversion. Das Portal ist statisch und erzeugt keine Datenbankabfragen.
Endpoint-Referenz
Die technischen Feldnamen bleiben in allen Sprachen gleich. Die OpenAPI-Datei beschreibt Felder, Pflichtangaben, Filter, Beispiele und Fehler.
| HTTP | Endpoint | Zugriff und Verhalten |
|---|---|---|
| GET | /api/v1/products | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/categories | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/collections | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/brands | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/customization-types | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/customization-groups | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/customization-options | catalog: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/upsert | catalog:write and/or prices:write; costs:write separately. external_id is scoped to the current connection. |
| GET | /api/v1/media | media:read |
| POST | /api/v1/media | media: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/me | Any valid key. |
| GET | /api/v1/changes | Resource read scope required. origin_connection_id supports loop prevention. Always persist next_cursor, even for an empty filtered page. |
| POST | /api/v1/imports | Write scopes for every record. |
| GET | /api/v1/imports | Any valid key; jobs from another connection remain hidden. |
| GET | /api/v1/jobs | Any 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}/retry | Required 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}/retry | Required 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/inventory | stock:read; stock package and module must be active. Detail balances contain revisions. Pagination cursor is inventory item UUID. |
| POST | /api/v1/inventory | stock: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. |
Datenmodelle
Die technischen Feldnamen bleiben in allen Sprachen gleich. Die OpenAPI-Datei beschreibt Felder, Pflichtangaben, Filter, Beispiele und Fehler.
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."
}