{
  "version": "v1",
  "base_url": "https://ctzrdkfnlwpnzmeycjpy.functions.supabase.co/public-api",
  "friendly_base_url": "https://api.pt24.at",
  "auth": {
    "methods": [
      {
        "type": "header",
        "name": "X-API-Key"
      },
      {
        "type": "query",
        "name": "key",
        "note": "Für Systeme die keine Custom Header senden können (z. B. 3CX External Phonebook)."
      }
    ]
  },
  "formats": [
    "json",
    "csv",
    "3cx",
    "vcard"
  ],
  "scopes": [
    "customers:read",
    "contacts:read",
    "orders:read",
    "positions:read",
    "orders:write",
    "customers:write",
    "contacts:write",
    "invoices:read",
    "suppliers:read",
    "suppliers:write",
    "messages:read",
    "messages:write",
    "*:read",
    "*:write"
  ],
  "enums": {
    "approval_status": [
      "pending",
      "approved",
      "rejected"
    ],
    "customer_type": [
      "company",
      "person"
    ],
    "invoice_status": [
      "draft",
      "sent",
      "paid",
      "partially_paid",
      "overdue",
      "cancelled"
    ],
    "order_status": [
      "draft",
      "pending_approval",
      "in_production",
      "completed",
      "cancelled",
      "pending_followup_question"
    ],
    "position_production_status": [
      "pending",
      "assigned",
      "in_progress",
      "completed",
      "pending_followup_question"
    ],
    "quote_status": [
      "draft",
      "sent",
      "accepted",
      "rejected",
      "expired"
    ]
  },
  "pagination": {
    "type": "cursor",
    "params": [
      "limit (default 100, max 1000)",
      "cursor (aus meta.next_cursor)"
    ],
    "example": {
      "first": "GET /v1/orders?limit=100",
      "next": "GET /v1/orders?limit=100&cursor=MjAyNi0wNS0...."
    }
  },
  "delta_sync": {
    "recommended_flow": [
      "1. Erster Aufruf ohne updated_since — alle Daten laden.",
      "2. server_time aus meta speichern.",
      "3. Nächster Aufruf mit ?updated_since={server_time} — nur Änderungen seither.",
      "4. Bei has_more=true mit cursor weiterblättern, bis next_cursor=null."
    ]
  },
  "idempotency": {
    "supported_endpoints": [
      "POST /v1/carts"
    ],
    "header": "Idempotency-Key",
    "ttl_hours": 24,
    "behavior": [
      "Client sendet eine eindeutige ID (UUID empfohlen) im Header 'Idempotency-Key'.",
      "Wird derselbe Key innerhalb von 24 h mit identischem Body erneut gesendet, liefert die API die ursprüngliche Response (Status inkl.) zurück — es entsteht kein doppelter Datensatz.",
      "Wird derselbe Key mit ABWEICHENDEM Body wiederverwendet, antwortet die API mit 409 idempotency_conflict.",
      "PATCH/DELETE/submit/release werden aktuell nicht per Idempotency-Key gesichert — sie sind aber ohnehin idempotent bzw. state-based (cart_locked-Guard)."
    ]
  },
  "rate_limits": {
    "note": "Aktuell kein hartes Rate-Limit, aber Fair-Use: max. ~10 req/s pro Key empfohlen. Alle Aufrufe werden in system_logs protokolliert."
  },
  "errors": [
    {
      "code": "missing_api_key",
      "http": 401,
      "description": "X-API-Key Header oder ?key=… fehlt."
    },
    {
      "code": "invalid_api_key",
      "http": 401,
      "description": "API-Key ist unbekannt."
    },
    {
      "code": "key_inactive",
      "http": 403,
      "description": "Der Key wurde deaktiviert."
    },
    {
      "code": "key_expired",
      "http": 403,
      "description": "Der Key ist abgelaufen."
    },
    {
      "code": "insufficient_scope",
      "http": 403,
      "description": "Der Key besitzt nicht den geforderten Scope."
    },
    {
      "code": "invalid_param",
      "http": 400,
      "description": "Ein Query-Parameter ist ungültig."
    },
    {
      "code": "validation_error",
      "http": 400,
      "description": "Request-Body hat fehlende oder ungültige Felder."
    },
    {
      "code": "invalid_json",
      "http": 400,
      "description": "Request-Body ist kein gültiges JSON."
    },
    {
      "code": "conflict",
      "http": 409,
      "description": "Eindeutigkeits-Konflikt (z. B. vendor_code existiert bereits)."
    },
    {
      "code": "cart_locked",
      "http": 409,
      "description": "Warenkorb wurde bereits eingereicht/freigegeben und kann nicht mehr verändert werden."
    },
    {
      "code": "not_submitted",
      "http": 409,
      "description": "Aktion setzt voraus, dass der Warenkorb bereits eingereicht wurde (cart_submitted_at IS NOT NULL)."
    },
    {
      "code": "not_accepted",
      "http": 409,
      "description": "Aktion setzt voraus, dass der Warenkorb vom Kunden akzeptiert wurde (cart_accepted_at IS NOT NULL)."
    },
    {
      "code": "already_released",
      "http": 409,
      "description": "Warenkorb ist bereits als Auftrag freigegeben (cart_released_at gesetzt)."
    },
    {
      "code": "position_not_in_cart",
      "http": 404,
      "description": "Position existiert nicht (mehr) in diesem Warenkorb."
    },
    {
      "code": "idempotency_conflict",
      "http": 409,
      "description": "Idempotency-Key wurde bereits mit anderem Request-Body verwendet. Neuen Key wählen oder Body identisch senden."
    },
    {
      "code": "not_found",
      "http": 404,
      "description": "Ressource existiert nicht."
    },
    {
      "code": "unknown_endpoint",
      "http": 404,
      "description": "Endpoint existiert nicht. Siehe /v1/meta."
    },
    {
      "code": "format_unsupported",
      "http": 400,
      "description": "Format für diesen Endpoint nicht unterstützt."
    },
    {
      "code": "method_not_allowed",
      "http": 405,
      "description": "HTTP-Methode für diesen Endpoint nicht erlaubt."
    },
    {
      "code": "query_failed",
      "http": 500,
      "description": "DB-Query ist fehlgeschlagen."
    },
    {
      "code": "internal_error",
      "http": 500,
      "description": "Unerwarteter Server-Fehler."
    }
  ],
  "endpoints": [
    {
      "method": "GET",
      "path": "/v1/health",
      "description": "Healthcheck. Validiert API-Key und liefert Server-Zeit.",
      "params": [],
      "requiredScope": null,
      "example": "curl -H 'X-API-Key: pt24_…' https://api.pt24.at/v1/health"
    },
    {
      "method": "GET",
      "path": "/v1/meta",
      "description": "Diese Doku. Liefert alle Endpoints, Felder und Parameter als JSON. Kein Auth nötig.",
      "params": [],
      "requiredScope": null,
      "example": "curl https://api.pt24.at/v1/meta"
    },
    {
      "method": "GET",
      "path": "/v1/customers",
      "description": "Liste aller Kunden mit voll ausformatiertem JSON: Stammdaten, Adressen, Primary-Contact und optional alle Kontakte / Stats.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "type",
          "type": "string (enum)",
          "description": "customer_type Enum (z.B. business, private)."
        },
        {
          "name": "archived",
          "type": "true | false | only",
          "description": "Default false (nur aktive)."
        },
        {
          "name": "with_contacts",
          "type": "boolean",
          "description": "Default false. true = alle Kontakte einbetten (primary_contact ist immer dabei)."
        },
        {
          "name": "with_stats",
          "type": "boolean",
          "description": "Default false. true = orders_count + last_order_date je Kunde."
        }
      ],
      "fields": [
        "id",
        "customer_number",
        "jtl_customer_id",
        "type",
        "company_name",
        "first_name",
        "last_name",
        "display_name",
        "email",
        "phone",
        "vat_id",
        "debtor_number",
        "language_iso",
        "currency_iso",
        "customer_group_id",
        "customer_category_id",
        "archived_at",
        "archived_reason",
        "billing_address",
        "delivery_address",
        "addresses",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "customers:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/customers?updated_since=2026-01-01T00:00:00Z&limit=200'"
    },
    {
      "method": "GET",
      "path": "/v1/customers/:id",
      "description": "Einzelner Kunde im selben Format wie /v1/customers. with_contacts / with_stats werden unterstützt.",
      "params": [
        {
          "name": "with_contacts",
          "type": "boolean",
          "description": "Default true für Detail-Ansicht."
        },
        {
          "name": "with_stats",
          "type": "boolean",
          "description": "Default true für Detail-Ansicht."
        }
      ],
      "fields": [
        "id",
        "customer_number",
        "jtl_customer_id",
        "type",
        "company_name",
        "first_name",
        "last_name",
        "display_name",
        "email",
        "phone",
        "vat_id",
        "debtor_number",
        "language_iso",
        "currency_iso",
        "customer_group_id",
        "customer_category_id",
        "archived_at",
        "archived_reason",
        "billing_address",
        "delivery_address",
        "addresses",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "customers:read",
      "example": "curl -H 'X-API-Key: …' https://api.pt24.at/v1/customers/UUID"
    },
    {
      "method": "GET",
      "path": "/v1/customers/:id/contacts",
      "description": "Alle Kontakte eines Kunden (kein Customer-Embed, da Kontext klar). Wird gebraucht, um `contact_id` (Ansprechpartner) oder `responsibles[].contact_id` (Verantwortliche) für POST /v1/carts zu ermitteln.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        }
      ],
      "fields": [
        "id",
        "customer_id",
        "jtl_contact_id",
        "jtl_staff_name",
        "full_name",
        "first_name",
        "last_name",
        "email",
        "phone",
        "mobile_phone",
        "position",
        "department",
        "is_primary",
        "avatar_url",
        "customer",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "contacts:read"
    },
    {
      "method": "GET",
      "path": "/v1/customers/:id/addresses",
      "description": "Alle Lieferadressen eines Kunden (customer_addresses). Wird gebraucht, um `delivery_address_id` für POST /v1/carts zu ermitteln. Sortiert nach is_default DESC.",
      "params": [],
      "fields": [
        "id",
        "type",
        "company",
        "salutation",
        "first_name",
        "last_name",
        "full_name",
        "street",
        "house_no",
        "address_addition",
        "zip",
        "city",
        "state",
        "country_code",
        "email",
        "phone",
        "is_default",
        "jtl_address_id"
      ],
      "requiredScope": "customers:read",
      "example": "curl -H 'X-API-Key: …' https://api.pt24.at/v1/customers/UUID/addresses"
    },
    {
      "method": "GET",
      "path": "/v1/customer-contacts",
      "description": "Liste aller Kundenkontakte mit eingebettetem Customer-Subset. Bestens geeignet für 3CX External Phonebook und CRM-Sync.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Nur Kontakte dieses Kunden."
        },
        {
          "name": "with_phone",
          "type": "boolean",
          "description": "Default true. Filtert Einträge ohne Telefon/Mobil."
        },
        {
          "name": "is_primary",
          "type": "boolean",
          "description": "Nur Hauptkontakte."
        }
      ],
      "fields": [
        "id",
        "customer_id",
        "jtl_contact_id",
        "jtl_staff_name",
        "full_name",
        "first_name",
        "last_name",
        "email",
        "phone",
        "mobile_phone",
        "position",
        "department",
        "is_primary",
        "avatar_url",
        "customer",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "contacts:read",
      "example": "curl 'https://api.pt24.at/v1/customer-contacts?format=3cx&key=pt24_…'"
    },
    {
      "method": "GET",
      "path": "/v1/customer-contacts/:id",
      "description": "Einzelner Kontakt mit eingebettetem Customer-Subset.",
      "params": [],
      "fields": [
        "id",
        "customer_id",
        "jtl_contact_id",
        "jtl_staff_name",
        "full_name",
        "first_name",
        "last_name",
        "email",
        "phone",
        "mobile_phone",
        "position",
        "department",
        "is_primary",
        "avatar_url",
        "customer",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "contacts:read"
    },
    {
      "method": "GET",
      "path": "/v1/orders",
      "description": "Liste aller Aufträge. Jeder Eintrag enthält das komplette Order-JSON inkl. Kunde, Adressen, Kontakt, Positionen (mit production-Block: status, assigned_to, completed_at, completed_by), responsibles[], fulfillments[] (Lieferungen mit Tracking + Items) und production_summary / fulfillment_summary. Bei Carts zusätzlich `quote` Block (submitted_at, accepted_at, active_share, share_history). Batch-Loading verhindert N+1.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Nur Aufträge dieses Kunden."
        },
        {
          "name": "status",
          "type": "string (enum)",
          "description": "order_status — draft, pending_approval, in_production, completed, cancelled, pending_followup_question."
        },
        {
          "name": "is_quote",
          "type": "boolean",
          "description": "true = nur Angebote/Carts, false = nur echte Aufträge. Default: false."
        },
        {
          "name": "archived",
          "type": "true | false | only",
          "description": "Default false (nur nicht-gelöschte)."
        },
        {
          "name": "include_cancelled",
          "type": "boolean",
          "description": "Default false."
        },
        {
          "name": "delivery_date_from",
          "type": "ISO date",
          "description": "delivery_date >= X."
        },
        {
          "name": "delivery_date_to",
          "type": "ISO date",
          "description": "delivery_date <= X."
        },
        {
          "name": "jtl_sync_status",
          "type": "string",
          "description": "Filter auf JTL-Sync-Status (z. B. \"synced\", \"pending\", \"error\", \"none\")."
        }
      ],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/orders?status=in_production&updated_since=2026-05-01T00:00:00Z'"
    },
    {
      "method": "GET",
      "path": "/v1/orders/:id",
      "description": "Einzelner Auftrag im vollen Format wie /v1/orders (inkl. production, fulfillments, responsibles).",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:read"
    },
    {
      "method": "GET",
      "path": "/v1/orders/:id/positions",
      "description": "Flache Positionsliste eines Auftrags inkl. production-Block (status, assigned_to, completed_at, completed_by) und BOM-Kinder auf gleicher Ebene (mit parent_position_id).",
      "params": [],
      "fields": [
        "id",
        "position_number",
        "description",
        "quantity",
        "unit_price_net",
        "tax_rate",
        "line_net",
        "line_gross",
        "production",
        "parent_position_id",
        "is_bom_child"
      ],
      "requiredScope": "orders:read"
    },
    {
      "method": "GET",
      "path": "/v1/positions/search",
      "description": "Globale Positions-Recherche über historische Aufträge/Warenkörbe: Beschreibung, Notizen, SKU, Kunde, Zeitraum, Preis- und Mengenbereiche. Für Kalkulationsvergleiche durch ZOE und externe Agenten.",
      "params": [
        {
          "name": "q",
          "type": "string",
          "description": "Freitext über Beschreibung, Notizen und SKU."
        },
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Nur Positionen dieses Kunden."
        },
        {
          "name": "sku",
          "type": "string",
          "description": "Exakte oder teilweise Artikelnummer."
        },
        {
          "name": "from",
          "type": "ISO timestamp/date",
          "description": "order.sales_order_date/created_at >= from."
        },
        {
          "name": "to",
          "type": "ISO timestamp/date",
          "description": "order.sales_order_date/created_at <= to."
        },
        {
          "name": "min_quantity",
          "type": "number",
          "description": "Mindestmenge."
        },
        {
          "name": "max_quantity",
          "type": "number",
          "description": "Maximalmenge."
        },
        {
          "name": "min_unit_price",
          "type": "number",
          "description": "Mindest-VK netto pro Einheit."
        },
        {
          "name": "max_unit_price",
          "type": "number",
          "description": "Maximal-VK netto pro Einheit."
        },
        {
          "name": "include_carts",
          "type": "boolean",
          "description": "Default true. false = nur freigegebene Aufträge."
        },
        {
          "name": "include_cancelled",
          "type": "boolean",
          "description": "Default false."
        },
        {
          "name": "limit",
          "type": "int",
          "description": "1–200, default 50."
        },
        {
          "name": "cursor",
          "type": "base64",
          "description": "Von meta.next_cursor der vorherigen Antwort."
        }
      ],
      "fields": [
        "id",
        "order_id",
        "order_number",
        "position_number",
        "sku",
        "title",
        "description",
        "notes",
        "internal_notes",
        "quantity",
        "unit",
        "unit_price_net",
        "purchase_price_net",
        "discount_pct",
        "tax_rate",
        "line_net",
        "line_gross",
        "sales_price_gross",
        "customer",
        "order",
        "production",
        "bom",
        "customer_product_id",
        "supplier_product_id",
        "configuration",
        "catalog_config",
        "source",
        "jtl_line_item_id",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "positions:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/positions/search?q=Rollup&customer_id=UUID&limit=50'"
    },
    {
      "method": "GET",
      "path": "/v1/orders/:id/fulfillments",
      "description": "Alle Lieferungen/Fulfillments eines Auftrags. Enthält type (pickup, shipment, delivery, installation, completion_only), status (draft, ready, in_transit, shipped, delivered, installed), Carrier + Tracking-No, scheduled_date/-time, delivered_at, dismantling-Kette und items[] (welche Positionen wie viel).",
      "params": [],
      "fields": [
        "id",
        "fulfillment_number",
        "type",
        "status",
        "carrier",
        "tracking_no",
        "scheduled_date",
        "shipping_date",
        "delivered_at",
        "items"
      ],
      "requiredScope": "orders:read"
    },
    {
      "method": "GET",
      "path": "/v1/invoices",
      "description": "Liste aller Rechnungen mit vollem JSON: Customer, Billing-Address, Positionen, Zahlungen, Totals (paid/outstanding). Ideal für Buchhaltungs-Sync.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Nur Rechnungen dieses Kunden."
        },
        {
          "name": "order_id",
          "type": "uuid",
          "description": "Nur Rechnungen dieses Auftrags."
        },
        {
          "name": "status",
          "type": "string (enum)",
          "description": "invoice_status — gültige Werte: draft, sent, paid, partially_paid, overdue, cancelled (siehe meta.enums.invoice_status)."
        },
        {
          "name": "issue_since",
          "type": "ISO date",
          "description": "issue_date >= X."
        },
        {
          "name": "due_before",
          "type": "ISO date",
          "description": "due_date <= X."
        }
      ],
      "fields": [
        "id",
        "invoice_number",
        "jtl_invoice_id",
        "status",
        "status_text",
        "is_cancelled",
        "order_id",
        "order_number",
        "quote_id",
        "customer_id",
        "issue_date",
        "due_date",
        "paid_date",
        "payment_method",
        "payment_reference",
        "notes",
        "customer",
        "billing_address",
        "contact",
        "totals",
        "positions_count",
        "positions",
        "payments",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "invoices:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/invoices?status=overdue'"
    },
    {
      "method": "GET",
      "path": "/v1/invoices/:id",
      "description": "Einzelne Rechnung im selben Format wie /v1/invoices.",
      "params": [],
      "fields": [
        "id",
        "invoice_number",
        "jtl_invoice_id",
        "status",
        "status_text",
        "is_cancelled",
        "order_id",
        "order_number",
        "quote_id",
        "customer_id",
        "issue_date",
        "due_date",
        "paid_date",
        "payment_method",
        "payment_reference",
        "notes",
        "customer",
        "billing_address",
        "contact",
        "totals",
        "positions_count",
        "positions",
        "payments",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "invoices:read"
    },
    {
      "method": "GET",
      "path": "/v1/suppliers",
      "description": "Liste aller Lieferanten. Jeder Eintrag = volles JSON inkl. Adresse, Kategorien, Keywords, Primary-Contact und optional alle Kontakte / Stats.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "active",
          "type": "boolean",
          "description": "Nur aktive (true) oder nur deaktivierte (false) Lieferanten."
        },
        {
          "name": "category",
          "type": "string",
          "description": "Filter auf supplier_categories (enthält diesen Eintrag)."
        },
        {
          "name": "with_contacts",
          "type": "boolean",
          "description": "Default false. true = alle Kontakte einbetten."
        },
        {
          "name": "with_stats",
          "type": "boolean",
          "description": "Default false. true = product_count + last_order_at je Lieferant."
        }
      ],
      "fields": [
        "id",
        "vendor_code",
        "name",
        "website_url",
        "email",
        "phone",
        "contact_person",
        "address",
        "street",
        "zip",
        "city",
        "country",
        "supplier_categories",
        "delivery_methods",
        "keywords",
        "payment_methods",
        "default_lead_time_days",
        "notes",
        "active",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/suppliers?active=true&with_contacts=true'"
    },
    {
      "method": "GET",
      "path": "/v1/suppliers/:id",
      "description": "Einzelner Lieferant. with_contacts / with_stats default true.",
      "params": [],
      "fields": [
        "id",
        "vendor_code",
        "name",
        "website_url",
        "email",
        "phone",
        "contact_person",
        "address",
        "street",
        "zip",
        "city",
        "country",
        "supplier_categories",
        "delivery_methods",
        "keywords",
        "payment_methods",
        "default_lead_time_days",
        "notes",
        "active",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:read"
    },
    {
      "method": "GET",
      "path": "/v1/suppliers/:id/contacts",
      "description": "Alle Kontakte eines Lieferanten.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        }
      ],
      "fields": [
        "id",
        "vendor_id",
        "name",
        "role",
        "email",
        "phone",
        "preferred",
        "supplier",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:read"
    },
    {
      "method": "POST",
      "path": "/v1/suppliers",
      "description": "Neuen Lieferanten anlegen. Pflichtfelder: vendor_code (unique), name. Alle übrigen Felder optional. Response = volles Supplier-JSON. Schreibt Audit-Log.",
      "params": [],
      "fields": [
        "id",
        "vendor_code",
        "name",
        "website_url",
        "email",
        "phone",
        "contact_person",
        "address",
        "street",
        "zip",
        "city",
        "country",
        "supplier_categories",
        "delivery_methods",
        "keywords",
        "payment_methods",
        "default_lead_time_days",
        "notes",
        "active",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:write",
      "example": "curl -X POST -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"vendor_code\":\"V-2026-001\",\"name\":\"Acme GmbH\",\"email\":\"info@acme.test\"}' https://api.pt24.at/v1/suppliers"
    },
    {
      "method": "PATCH",
      "path": "/v1/suppliers/:id",
      "description": "Partial-Update. Nur gesendete Felder werden geändert. vendor_code ist immutable.",
      "params": [],
      "fields": [
        "id",
        "vendor_code",
        "name",
        "website_url",
        "email",
        "phone",
        "contact_person",
        "address",
        "street",
        "zip",
        "city",
        "country",
        "supplier_categories",
        "delivery_methods",
        "keywords",
        "payment_methods",
        "default_lead_time_days",
        "notes",
        "active",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:write"
    },
    {
      "method": "DELETE",
      "path": "/v1/suppliers/:id",
      "description": "Soft-Delete: setzt active=false. Vollständiges Löschen aus Datenschutzgründen nur über UI/Admin.",
      "params": [],
      "fields": [],
      "requiredScope": "suppliers:write"
    },
    {
      "method": "GET",
      "path": "/v1/supplier-contacts",
      "description": "Liste aller Lieferantenkontakte mit eingebettetem Supplier-Subset (vendor_code, name).",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "vendor_id",
          "type": "uuid",
          "description": "Nur Kontakte dieses Lieferanten."
        },
        {
          "name": "preferred",
          "type": "boolean",
          "description": "Nur bevorzugte Kontakte."
        }
      ],
      "fields": [
        "id",
        "vendor_id",
        "name",
        "role",
        "email",
        "phone",
        "preferred",
        "supplier",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:read"
    },
    {
      "method": "GET",
      "path": "/v1/supplier-contacts/:id",
      "description": "Einzelner Lieferantenkontakt mit eingebettetem Supplier-Subset.",
      "params": [],
      "fields": [
        "id",
        "vendor_id",
        "name",
        "role",
        "email",
        "phone",
        "preferred",
        "supplier",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:read"
    },
    {
      "method": "POST",
      "path": "/v1/supplier-contacts",
      "description": "Neuen Lieferantenkontakt anlegen. Pflichtfelder: vendor_id, name.",
      "params": [],
      "fields": [
        "id",
        "vendor_id",
        "name",
        "role",
        "email",
        "phone",
        "preferred",
        "supplier",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:write"
    },
    {
      "method": "PATCH",
      "path": "/v1/supplier-contacts/:id",
      "description": "Partial-Update Lieferantenkontakt.",
      "params": [],
      "fields": [
        "id",
        "vendor_id",
        "name",
        "role",
        "email",
        "phone",
        "preferred",
        "supplier",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "suppliers:write"
    },
    {
      "method": "DELETE",
      "path": "/v1/supplier-contacts/:id",
      "description": "Hard-Delete des Lieferantenkontakts (Kontakte enthalten keine kritischen Belege).",
      "params": [],
      "fields": [],
      "requiredScope": "suppliers:write"
    },
    {
      "method": "POST",
      "path": "/v1/customers",
      "description": "Neuen Kunden anlegen. Pflicht: type (company|person), bei company zusätzlich company_name, bei person last_name. Optional: email, phone, vat_id, debtor_number, customer_number, customer_group_id, customer_category_id. Nach dem Anlegen wird ein JTL-Write-Job (jtl_write_customer) eingereiht — jtl_customer_id kommt asynchron zurück. Schreibt Audit-Log.",
      "params": [],
      "fields": [
        "id",
        "customer_number",
        "jtl_customer_id",
        "type",
        "company_name",
        "first_name",
        "last_name",
        "display_name",
        "email",
        "phone",
        "vat_id",
        "debtor_number",
        "language_iso",
        "currency_iso",
        "customer_group_id",
        "customer_category_id",
        "archived_at",
        "archived_reason",
        "billing_address",
        "delivery_address",
        "addresses",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "customers:write",
      "example": "curl -X POST -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"type\":\"company\",\"company_name\":\"Acme GmbH\",\"email\":\"office@acme.test\"}' https://api.pt24.at/v1/customers"
    },
    {
      "method": "PATCH",
      "path": "/v1/customers/:id",
      "description": "Partial-Update Kundenstammdaten. Nur gesendete Felder ändern. Kunden mit is_locked=true werden abgelehnt (423) — Sperre vorher via /unlock aufheben. Nach Update wird ein JTL-Write-Job eingereiht.",
      "params": [],
      "fields": [
        "id",
        "customer_number",
        "jtl_customer_id",
        "type",
        "company_name",
        "first_name",
        "last_name",
        "display_name",
        "email",
        "phone",
        "vat_id",
        "debtor_number",
        "language_iso",
        "currency_iso",
        "customer_group_id",
        "customer_category_id",
        "archived_at",
        "archived_reason",
        "billing_address",
        "delivery_address",
        "addresses",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "customers:write"
    },
    {
      "method": "DELETE",
      "path": "/v1/customers/:id",
      "description": "Archiviert einen Kunden (setzt archived_at + archived_reason). Löscht ihn NICHT physisch. Blockiert (409), wenn aktive Aufträge vorhanden.",
      "params": [],
      "fields": [],
      "requiredScope": "customers:write"
    },
    {
      "method": "POST",
      "path": "/v1/customers/:id/lock",
      "description": "Sperrt einen Kunden manuell (is_locked=true, lock_source='manual', locked_reason optional). Gesperrte Kunden können in Warenkörben und Aufträgen nicht mehr verwendet werden. Synced zu JTL.",
      "params": [],
      "fields": [
        "id",
        "customer_number",
        "jtl_customer_id",
        "type",
        "company_name",
        "first_name",
        "last_name",
        "display_name",
        "email",
        "phone",
        "vat_id",
        "debtor_number",
        "language_iso",
        "currency_iso",
        "customer_group_id",
        "customer_category_id",
        "archived_at",
        "archived_reason",
        "billing_address",
        "delivery_address",
        "addresses",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "customers:write"
    },
    {
      "method": "POST",
      "path": "/v1/customers/:id/unlock",
      "description": "Hebt die manuelle Sperre auf. JTL-gesperrte Kunden (lock_source='jtl') können nur über JTL entsperrt werden — Aufruf wird dann mit 409 abgewiesen.",
      "params": [],
      "fields": [
        "id",
        "customer_number",
        "jtl_customer_id",
        "type",
        "company_name",
        "first_name",
        "last_name",
        "display_name",
        "email",
        "phone",
        "vat_id",
        "debtor_number",
        "language_iso",
        "currency_iso",
        "customer_group_id",
        "customer_category_id",
        "archived_at",
        "archived_reason",
        "billing_address",
        "delivery_address",
        "addresses",
        "primary_contact",
        "contacts",
        "stats",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "customers:write"
    },
    {
      "method": "POST",
      "path": "/v1/customers/:id/contacts",
      "description": "Neuen Ansprechpartner anlegen. Pflicht: full_name. Optional: email, phone, mobile_phone, position, department, is_primary. Löst automatisch JTL-Sync (jtl_write_customer_contact) für den Kunden aus.",
      "params": [],
      "fields": [
        "id",
        "customer_id",
        "jtl_contact_id",
        "jtl_staff_name",
        "full_name",
        "first_name",
        "last_name",
        "email",
        "phone",
        "mobile_phone",
        "position",
        "department",
        "is_primary",
        "avatar_url",
        "customer",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "contacts:write",
      "example": "curl -X POST -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"full_name\":\"Max Muster\",\"email\":\"max@acme.test\",\"is_primary\":true}' https://api.pt24.at/v1/customers/UUID/contacts"
    },
    {
      "method": "POST",
      "path": "/v1/customer-contacts",
      "description": "Alias — Kontakt anlegen mit customer_id im Body (gleiche Response wie POST /v1/customers/:id/contacts).",
      "params": [],
      "fields": [
        "id",
        "customer_id",
        "jtl_contact_id",
        "jtl_staff_name",
        "full_name",
        "first_name",
        "last_name",
        "email",
        "phone",
        "mobile_phone",
        "position",
        "department",
        "is_primary",
        "avatar_url",
        "customer",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "contacts:write"
    },
    {
      "method": "PATCH",
      "path": "/v1/customer-contacts/:id",
      "description": "Partial-Update Kontakt. customer_id ist immutable. Löst JTL-Sync für den Kunden aus.",
      "params": [],
      "fields": [
        "id",
        "customer_id",
        "jtl_contact_id",
        "jtl_staff_name",
        "full_name",
        "first_name",
        "last_name",
        "email",
        "phone",
        "mobile_phone",
        "position",
        "department",
        "is_primary",
        "avatar_url",
        "customer",
        "created_at",
        "updated_at"
      ],
      "requiredScope": "contacts:write"
    },
    {
      "method": "DELETE",
      "path": "/v1/customer-contacts/:id",
      "description": "Löscht Kontakt. Blockiert (409), wenn ein App-User an diesen Kontakt gebunden ist — App-User zuerst über Backend entfernen. Löst JTL-Sync aus.",
      "params": [],
      "fields": [],
      "requiredScope": "contacts:write"
    },
    {
      "method": "POST",
      "path": "/v1/customers/:id/addresses",
      "description": "Neue Adresse anlegen. Pflicht: type (invoice|delivery|other), sinnvollerweise street/zip/city/country_code. is_default=true setzt sie zusätzlich als Standard-Adresse des Typs. Löst JTL-Sync (jtl_write_customer_address) aus.",
      "params": [],
      "fields": [
        "id",
        "type",
        "company",
        "first_name",
        "last_name",
        "street",
        "house_no",
        "zip",
        "city",
        "country_code",
        "email",
        "phone",
        "is_default",
        "jtl_address_id"
      ],
      "requiredScope": "customers:write"
    },
    {
      "method": "PATCH",
      "path": "/v1/customer-addresses/:id",
      "description": "Partial-Update einer Adresse. customer_id ist immutable. Löst JTL-Sync aus.",
      "params": [],
      "fields": [],
      "requiredScope": "customers:write"
    },
    {
      "method": "DELETE",
      "path": "/v1/customer-addresses/:id",
      "description": "Löscht Adresse. Blockiert (409), wenn sie als default_invoice_address / default_delivery_address des Kunden gesetzt oder von aktiven Aufträgen referenziert ist. Löst JTL-Sync aus.",
      "params": [],
      "fields": [],
      "requiredScope": "customers:write"
    },
    {
      "method": "GET",
      "path": "/v1/carts",
      "description": "Liste aller Warenkörbe (is_cart=true). Selbe Struktur wie /v1/orders — inkl. Kunde, Adressen, Positionen. Alias für /v1/orders?is_quote=true.",
      "params": [
        {
          "name": "updated_since",
          "type": "ISO-8601 timestamp",
          "description": "Liefert nur Datensätze mit updated_at > X. Hauptmechanismus für Delta-Sync."
        },
        {
          "name": "created_since",
          "type": "ISO-8601 timestamp",
          "description": "Filter auf created_at."
        },
        {
          "name": "id",
          "type": "uuid",
          "description": "Einzel-ID-Filter."
        },
        {
          "name": "ids",
          "type": "uuid,uuid,...",
          "description": "Mehrere IDs (Komma-Liste)."
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltextsuche über Name/Nummer/E-Mail/Telefon."
        },
        {
          "name": "format",
          "type": "json | csv | 3cx | vcard",
          "description": "Ausgabeformat. Default: json. 3cx/vcard nur für /v1/customer-contacts."
        },
        {
          "name": "fields",
          "type": "feld1,feld2",
          "description": "Sparse Fieldsets — nur diese Top-Level-Felder zurückgeben (json)."
        },
        {
          "name": "sort",
          "type": "feld | -feld",
          "description": "Sortierung. Minus = absteigend. Default: -updated_at."
        },
        {
          "name": "limit",
          "type": "1..1000",
          "description": "Max. Datensätze pro Seite. Default 100."
        },
        {
          "name": "cursor",
          "type": "opaque string",
          "description": "Cursor aus meta.next_cursor der vorherigen Antwort."
        },
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Nur Warenkörbe dieses Kunden."
        },
        {
          "name": "status",
          "type": "string (enum)",
          "description": "order_status Filter."
        },
        {
          "name": "submitted",
          "type": "true | false | all",
          "description": "true = eingereicht, false = Entwurf. Default all."
        }
      ],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/carts?limit=50'"
    },
    {
      "method": "GET",
      "path": "/v1/carts/:id",
      "description": "Einzelner Warenkorb im vollen Order-JSON-Format.",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:read"
    },
    {
      "method": "PATCH",
      "path": "/v1/carts/:id",
      "description": "Kopfdaten eines Warenkorbs ändern (offer_note, Projekt, Notizen, Kontakt, Liefer-Infos, Sprache/Währung/Steuer-Modus). Nur solange der Warenkorb nicht eingereicht/freigegeben ist. Wenn der Warenkorb bereits als Auftrag freigegeben wurde → 409 `already_released` (dann `PATCH /v1/orders/:id` verwenden). customer_id ist nicht änderbar. Nur wirklich geänderte Felder werden geschrieben und im Audit-Log als Diff protokolliert.",
      "params": [
        {
          "name": "project_name",
          "type": "string",
          "description": "Projekt/Titel. Leerer String → wird auf null gesetzt."
        },
        {
          "name": "notes",
          "type": "string",
          "description": "⚠️ KUNDENSICHTBAR — wird in Angebots-/Auftrags-PDF und im Portal gedruckt. Für rein interne Vermerke `internal_notes` verwenden."
        },
        {
          "name": "internal_notes",
          "type": "string",
          "description": "🔒 NUR INTERN — nie kundensichtbar (kein PDF, kein Portal, kein JTL-Sync)."
        },
        {
          "name": "offer_note",
          "type": "string",
          "description": "Kundensichtbarer Hinweistext für Angebots-PDF (Kopf)."
        },
        {
          "name": "customer_reference",
          "type": "string",
          "description": "Kundenreferenz / Bestellnummer des Kunden."
        },
        {
          "name": "contact_id",
          "type": "uuid",
          "description": "Ansprechpartner beim Kunden. Muss zum Kunden des Warenkorbs gehören. null = entfernen."
        },
        {
          "name": "delivery_date",
          "type": "ISO date",
          "description": "Wunsch-Liefertermin. null = entfernen."
        },
        {
          "name": "delivery_address_id",
          "type": "uuid",
          "description": "Lieferadresse — muss zum Kunden gehören. null = entfernen."
        },
        {
          "name": "delivery_urgency",
          "type": "enum",
          "description": "ok | soon | overdue."
        },
        {
          "name": "language_iso",
          "type": "string",
          "description": "z. B. \"de\", \"en\"."
        },
        {
          "name": "currency_iso",
          "type": "string",
          "description": "z. B. \"EUR\"."
        },
        {
          "name": "tax_mode",
          "type": "string",
          "description": "Erlaubt: standard, reverse_charge, intra_community, tax_free_export. `net`/`gross` werden abgelehnt (bezeichnen keine Steuermodi)."
        }
      ],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write",
      "example": "curl -X PATCH -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"offer_note\":\"Preise gültig 30 Tage.\",\"project_name\":\"Messe Q3\"}' https://api.pt24.at/v1/carts/{cart_id}"
    },
    {
      "method": "PATCH",
      "path": "/v1/orders/:id",
      "description": "Kopfdaten eines freigegebenen Auftrags ändern (nur unkritische Felder — keine Positionen/Preise). Genau dieselben änderbaren Felder wie bei PATCH /v1/carts/:id. Änderungen werden im Audit-Log als Diff protokolliert.",
      "params": [
        {
          "name": "project_name",
          "type": "string",
          "description": "Projekt/Titel."
        },
        {
          "name": "notes",
          "type": "string",
          "description": "⚠️ KUNDENSICHTBAR (siehe PATCH /v1/carts/:id)."
        },
        {
          "name": "internal_notes",
          "type": "string",
          "description": "🔒 Nur intern."
        },
        {
          "name": "customer_reference",
          "type": "string",
          "description": "Kundenreferenz."
        },
        {
          "name": "delivery_date",
          "type": "ISO date",
          "description": "Wunsch-Liefertermin."
        },
        {
          "name": "delivery_urgency",
          "type": "enum",
          "description": "ok | soon | overdue."
        }
      ],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write",
      "example": "curl -X PATCH -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"internal_notes\":\"Beschaffung: Lieferant X, EK 42€\"}' https://api.pt24.at/v1/orders/{order_id}"
    },
    {
      "method": "POST",
      "path": "/v1/carts",
      "description": "Neuen Warenkorb anlegen. Alle Felder optional — ohne customer_id/customer_number wird der Temp-Kunde 0000000 verwendet. Positionen können direkt inline mitgegeben werden (unit_price ist Netto; total_price wird berechnet falls fehlend). Response = volles Order-JSON + Location-Header. Schreibt Audit-Log. Unterstützt `Idempotency-Key` Header (24 h TTL): identischer Body → identische Response, abweichender Body → 409 idempotency_conflict.",
      "params": [
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Bevorzugte Kundenreferenz."
        },
        {
          "name": "customer_number",
          "type": "string",
          "description": "Alternative zu customer_id — z. B. \"K-12345\" oder \"0000000\" für Temp-Kunde."
        },
        {
          "name": "project_name",
          "type": "string",
          "description": "Default \"Neuer Warenkorb\"."
        },
        {
          "name": "notes",
          "type": "string",
          "description": "⚠️ KUNDENSICHTBAR — wird im Angebots-/Auftrags-PDF und im Portal gedruckt."
        },
        {
          "name": "internal_notes",
          "type": "string",
          "description": "🔒 Rein interne Notiz — nie kundensichtbar."
        },
        {
          "name": "offer_note",
          "type": "string",
          "description": "Kundensichtbare Notiz für Angebot/PDF."
        },
        {
          "name": "customer_reference",
          "type": "string",
          "description": "Kundenreferenz / Bestellnummer des Kunden."
        },
        {
          "name": "contact_id",
          "type": "uuid",
          "description": "Ansprechpartner beim Kunden (aus GET /v1/customers/:id/contacts)."
        },
        {
          "name": "delivery_date",
          "type": "ISO date",
          "description": "Wunsch-Liefertermin."
        },
        {
          "name": "delivery_address_id",
          "type": "uuid",
          "description": "Lieferadresse — muss zum Kunden gehören (aus GET /v1/customers/:id/addresses)."
        },
        {
          "name": "delivery_urgency",
          "type": "enum",
          "description": "ok | soon | overdue."
        },
        {
          "name": "language_iso",
          "type": "string",
          "description": "z. B. \"de\", \"en\"."
        },
        {
          "name": "currency_iso",
          "type": "string",
          "description": "z. B. \"EUR\"."
        },
        {
          "name": "tax_mode",
          "type": "string",
          "description": "Erlaubt: standard, reverse_charge, intra_community, tax_free_export. `net`/`gross` werden abgelehnt."
        },
        {
          "name": "responsibles",
          "type": "array",
          "description": "Verantwortliche des Auftrags. Format: [{ contact_id?: uuid, staff_user_id?: uuid, role?: string }]. Genau EINES von contact_id ODER staff_user_id pro Eintrag. Kontakte müssen zum Kunden gehören; staff_user_id muss ein Platform-Staff- oder KI-Agent-User sein."
        },
        {
          "name": "positions",
          "type": "array",
          "description": "Optional. Jede Position: {description*, quantity*, unit?, unit_price? (VK netto), sales_price_gross? (VK brutto), purchase_price_net? (EK netto), tax_rate?, sku?, notes? (KUNDENSICHTBAR), internal_notes? (nur intern), discount?, is_optional?, variant_group?, customer_product_id?, supplier_product_id?, configuration?, sub_positions?}. * = Pflicht. **Automatische BOM**: Wenn customer_product_id gesetzt ist und das Produkt eine BOM-Vorlage hat, werden Sub-Positionen automatisch serverseitig expandiert. **Manuelle BOM**: Alternativ kann sub_positions[] direkt mitgegeben werden (max. 50 Kinder). Auf Kindern sind sub_positions, variant_group und is_optional verboten."
        }
      ],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write",
      "example": "curl -X POST -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"customer_number\":\"0000000\",\"project_name\":\"Designwahl\",\"positions\":[{\"description\":\"Marlenes Design 1\",\"quantity\":1,\"variant_group\":\"Designwahl\",\"sub_positions\":[{\"description\":\"Folierung\",\"quantity\":1,\"unit_price\":120},{\"description\":\"Grafik\",\"quantity\":1,\"unit_price\":80}]},{\"description\":\"Marlenes Design 3\",\"quantity\":1,\"variant_group\":\"Designwahl\",\"sub_positions\":[{\"description\":\"Folierung\",\"quantity\":1,\"unit_price\":150},{\"description\":\"Grafik\",\"quantity\":1,\"unit_price\":90}]}]}' https://api.pt24.at/v1/carts"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/positions",
      "description": "Weitere Positionen an bestehenden Warenkorb anhängen. Body: { positions: [...] }. Position-Nummern werden automatisch fortgezählt. Unterstützt sowohl automatische BOM (customer_product_id mit Template) als auch manuelle BOM (sub_positions[] direkt am Parent, max. 50 Kinder). Kinder erben Nummerierung direkt nach dem Parent.",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "PATCH",
      "path": "/v1/carts/:cart_id/positions/:position_id",
      "description": "Einzelne Cart-Position ändern. Änderbare Felder: description, quantity, unit, unit_price (VK netto), sales_price_gross (VK brutto), purchase_price_net (EK netto), tax_rate, sku, notes, discount, is_optional, variant_group (String/null), customer_product_id, supplier_product_id, configuration. Änderungen an quantity/customer_product_id/configuration triggern eine BOM-Neuexpansion (alte Sub-Positionen werden soft-deleted). Nur möglich solange Warenkorb nicht eingereicht (cart_submitted_at IS NULL) und nicht freigegeben.",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write",
      "example": "curl -X PATCH -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"quantity\":5}' https://api.pt24.at/v1/carts/{cart_id}/positions/{position_id}"
    },
    {
      "method": "DELETE",
      "path": "/v1/carts/:cart_id/positions/:position_id",
      "description": "Cart-Position soft-löschen (setzt deleted_at). Kaskadiert auf Sub-Positionen (BOM-Kinder). Nur möglich solange Warenkorb nicht eingereicht.",
      "params": [],
      "fields": [
        "id",
        "deleted_at",
        "cascaded_children"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/submit",
      "description": "Warenkorb einreichen — setzt cart_submitted_at und (optional) Status. Body: { note?: string }. Optional: ?release=true wandelt den Warenkorb direkt in einen Auftrag um (is_cart=false, order_number neu, cart_released_at gesetzt).",
      "params": [
        {
          "name": "release",
          "type": "boolean",
          "description": "true = direkt als Auftrag freigeben (überspringt Angebotsphase, generiert echte order_number)."
        }
      ],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/release",
      "description": "Freigabe eines bereits eingereichten und vom Kunden akzeptierten Angebots. Wandelt Cart in Auftrag um (setzt cart_released_at, is_cart=false, vergibt neue order_number). Nur erlaubt wenn cart_submitted_at + cart_accepted_at gesetzt und cart_released_at noch NULL. Body optional: { note?: string, delivery_date?: string (YYYY-MM-DD, setzt delivery_date) }.",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "POST",
      "path": "/v1/orders/:id/jtl-sync",
      "description": "Recovery-Endpoint: schiebt eine bereits freigegebene Order (is_cart=false, cart_released_at gesetzt, jtl_order_id noch NULL) nachträglich in die JTL-Sync-Queue. Idempotent — wenn bereits eine offene salesorder_create-Session existiert, wird nichts neu angelegt. Body optional: { note?: string, delivery_date?: string }.",
      "params": [],
      "fields": [
        "order_id",
        "order_number",
        "jtl_sync"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/send",
      "description": "Angebot verschicken: erzeugt einen quote_share_token (30 Tage Gültigkeit, konfigurierbar) und verschickt optional E-Mails an die angegebenen Empfänger via `send-notification-email` (Template quote_offer/quote_revised). Setzt cart_visibility=both. Response enthält den öffentlichen Link (`public_url`), den Token und Send-Status pro Empfänger. Wiederholter Aufruf mit `token_id` → Resend mit demselben Token (kein neuer Link).",
      "params": [
        {
          "name": "recipients",
          "type": "array",
          "description": "Optional. Liste `[{ email: string, name?: string }]`. Wenn leer → nur Token/Link erzeugen, ohne E-Mail-Versand."
        },
        {
          "name": "custom_subject",
          "type": "string",
          "description": "Optional. Betreff überschreiben."
        },
        {
          "name": "custom_message",
          "type": "string",
          "description": "Optional. Einleitungstext für die E-Mail."
        },
        {
          "name": "expires_in_days",
          "type": "integer",
          "description": "Default 30. Gültigkeit des Links."
        },
        {
          "name": "allow_partial_acceptance",
          "type": "boolean",
          "description": "Default false. Wenn true darf der Kunde einzelne Positionen ablehnen."
        },
        {
          "name": "attach_pdf",
          "type": "boolean",
          "description": "Default true. Angebots-PDF als E-Mail-Anhang."
        },
        {
          "name": "token_id",
          "type": "uuid",
          "description": "Optional. Wenn gesetzt → Resend über bestehenden Token (Link bleibt gleich)."
        },
        {
          "name": "is_revision",
          "type": "boolean",
          "description": "Default: automatisch (true wenn cart_change_requested_at oder cart_rejected_at gesetzt). Steuert Template (quote_revised vs quote_offer)."
        }
      ],
      "fields": [
        "token",
        "public_url",
        "expires_at",
        "recipients_sent",
        "recipients_failed"
      ],
      "requiredScope": "orders:write",
      "example": "curl -X POST -H 'X-API-Key: …' -H 'Content-Type: application/json' -d '{\"recipients\":[{\"email\":\"kunde@firma.at\",\"name\":\"Max Muster\"}],\"expires_in_days\":14}' https://api.pt24.at/v1/carts/{cart_id}/send"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/revoke-share",
      "description": "Alle aktiven Share-Tokens eines Angebots widerrufen (is_revoked=true). Nach dem Widerruf kann der öffentliche Link nicht mehr geöffnet werden. Idempotent — bereits widerrufene Tokens werden ignoriert.",
      "params": [
        {
          "name": "token_id",
          "type": "uuid",
          "description": "Optional. Nur diesen Token widerrufen. Ohne Parameter werden alle aktiven Tokens des Warenkorbs widerrufen."
        }
      ],
      "fields": [
        "revoked_count"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "GET",
      "path": "/v1/carts/:id/quote-status",
      "description": "Live-Status eines Angebots für Polling: submitted_at, accepted_at, rejected_at, change_requested_at, active_share (mit access_count, download_count, accessed_at) und position-level acceptance-Zustand. Perfekt für externe Systeme die auf Kundenreaktion warten.",
      "params": [],
      "fields": [
        "submitted_at",
        "accepted_at",
        "rejected_at",
        "change_requested_at",
        "active_share",
        "positions_acceptance"
      ],
      "requiredScope": "orders:read"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/accept",
      "description": "Angebot programmatisch akzeptieren (z. B. Rückkanal aus externem CRM/Portal). Setzt cart_accepted_at, optional pro-Position acceptance_status. Body: { accepted_position_ids?: uuid[], declined_position_ids?: uuid[], note?: string }.",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "POST",
      "path": "/v1/carts/:id/reject",
      "description": "Angebot ablehnen. Setzt cart_rejected_at + optional Ablehnungsgrund in notes. Body: { reason?: string, requested_changes?: string }. Wenn `requested_changes` gesetzt → cart_change_requested_at + Message wird an internes Message-Thread angehängt.",
      "params": [],
      "fields": [
        "id",
        "order_number",
        "external_number",
        "billing_number",
        "is_quote",
        "is_cart",
        "is_cancelled",
        "status",
        "status_text",
        "payment_status",
        "jtl_sync_status",
        "source",
        "project_name",
        "customer_reference",
        "notes",
        "internal_notes",
        "offer_note",
        "language_iso",
        "currency_iso",
        "tax_mode",
        "tax_text",
        "requires_prepayment",
        "created_at",
        "updated_at",
        "sales_order_date",
        "delivery_date",
        "shipping_date",
        "delivered_date",
        "delivery_urgency",
        "customer",
        "billing_address",
        "delivery_address",
        "contact",
        "totals",
        "payment",
        "positions_count",
        "positions",
        "meta"
      ],
      "requiredScope": "orders:write"
    },
    {
      "method": "GET",
      "path": "/v1/messages",
      "description": "Globale Nachrichten-Suche/Feed über alle Aufträge. Filterbar nach Zeitraum, Kunde, Auftrag, Autor, intern/extern, Nachrichtentyp und Volltext. Keyset-Paginierung via ?cursor.",
      "params": [
        {
          "name": "from",
          "type": "ISO timestamp",
          "description": "created_at >= from"
        },
        {
          "name": "to",
          "type": "ISO timestamp",
          "description": "created_at <= to"
        },
        {
          "name": "order_id",
          "type": "uuid",
          "description": "Nur Nachrichten dieses Auftrags."
        },
        {
          "name": "customer_id",
          "type": "uuid",
          "description": "Nur Nachrichten zu Aufträgen dieses Kunden."
        },
        {
          "name": "author_user_id",
          "type": "uuid",
          "description": "Nur Nachrichten dieses Autors."
        },
        {
          "name": "is_internal",
          "type": "boolean",
          "description": "true = nur interne, false = nur externe Nachrichten."
        },
        {
          "name": "message_type",
          "type": "string (enum)",
          "description": "chat | system | file_comment | change_request | rejection"
        },
        {
          "name": "q",
          "type": "string",
          "description": "Volltext (ILIKE) über body."
        },
        {
          "name": "limit",
          "type": "int",
          "description": "1–200, default 50."
        },
        {
          "name": "cursor",
          "type": "base64",
          "description": "Von meta.next_cursor der vorherigen Antwort."
        }
      ],
      "fields": [
        "id",
        "order_id",
        "message_type",
        "user_id",
        "user_name",
        "body",
        "attachments",
        "mentions",
        "is_internal",
        "parent_message_id",
        "created_at"
      ],
      "requiredScope": "messages:read",
      "example": "curl -H 'X-API-Key: …' 'https://api.pt24.at/v1/messages?from=2026-07-01T00:00:00Z&q=Freigabe&limit=100'"
    },
    {
      "method": "GET",
      "path": "/v1/orders/:id/messages",
      "description": "Alle Nachrichten eines Auftrags, threaded (replies eingebettet unter parent).",
      "params": [],
      "fields": [
        "id",
        "message_type",
        "body",
        "user_id",
        "user_name",
        "is_internal",
        "replies",
        "created_at"
      ],
      "requiredScope": "messages:read"
    },
    {
      "method": "POST",
      "path": "/v1/orders/:id/messages",
      "description": "Neue Nachricht anlegen. Body: { body: string, message_type?: chat|system|file_comment|change_request|rejection, is_internal?: boolean, parent_message_id?: uuid, mentions?: any[], attachments?: any[] }. Autor wird aus X-Acting-User Header (UUID oder E-Mail) oder dem am Key hinterlegten acting_user_id gezogen. Schreibt Audit-Log mit actor_user_id.",
      "params": [],
      "fields": [],
      "requiredScope": "messages:write",
      "example": "curl -X POST -H 'X-API-Key: …' -H 'X-Acting-User: max@pt24.at' -H 'Content-Type: application/json' -d '{\"body\":\"Bitte prüfen\",\"is_internal\":true}' https://api.pt24.at/v1/orders/UUID/messages"
    },
    {
      "method": "GET",
      "path": "/v1/messages/:id",
      "description": "Einzelne Nachricht.",
      "params": [],
      "fields": [],
      "requiredScope": "messages:read"
    },
    {
      "method": "PATCH",
      "path": "/v1/messages/:id",
      "description": "Nachricht bearbeiten (nur eigene: acting_user muss der Autor sein). Bearbeitbare Felder: body, is_internal, attachments, mentions.",
      "params": [],
      "fields": [],
      "requiredScope": "messages:write"
    },
    {
      "method": "DELETE",
      "path": "/v1/messages/:id",
      "description": "Nachricht löschen (nur eigene).",
      "params": [],
      "fields": [],
      "requiredScope": "messages:write"
    }
  ],
  "server_time": "2026-07-31T14:02:55.296Z"
}