{
  "openapi": "3.0.3",
  "info": {
    "title": "Berti Graf NL Website API",
    "version": "2.0.1",
    "description": "Bertigraf NL integration. Inventory reads use JTL warehouse 12 only. Authenticated mail is sent from info@bertigraf.nl. New orders and confirmations currently return 503."
  },
  "servers": [
    {
      "url": "https://backend.bertigraf.nl",
      "description": "Cloudflare → Windows nginx TLS → 127.0.0.1:5191"
    }
  ],
  "security": [
    {
      "partnerToken": []
    }
  ],
  "components": {
    "securitySchemes": {
      "partnerToken": {
        "type": "http",
        "scheme": "bearer",
        "description": "Bearer access token, normally issued by POST /api/v1/auth/token to a separately approved server client. One-hour lifetime; renew using private client credentials. Scope is fixed by the administrator. Existing independent legacy keys retain their original expiry."
      }
    },
    "schemas": {
      "OrderRequest": {
        "type": "object",
        "required": [
          "portal",
          "account",
          "region",
          "externalOrderId",
          "currency",
          "lines",
          "customer",
          "billingAddress",
          "shippingAddress"
        ],
        "properties": {
          "portal": {
            "type": "string",
            "enum": [
              "bertigraf"
            ]
          },
          "account": {
            "type": "string",
            "maxLength": 100
          },
          "region": {
            "type": "string",
            "pattern": "^[A-Z]{2}$"
          },
          "externalOrderId": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "currency": {
            "type": "string",
            "enum": [
              "EUR"
            ]
          },
          "lines": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": [
                "externalLineId",
                "sku",
                "quantity",
                "unitPriceGross",
                "vatPercent"
              ],
              "properties": {
                "externalLineId": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 100
                },
                "sku": {
                  "type": "string",
                  "minLength": 1,
                  "maxLength": 100
                },
                "quantity": {
                  "type": "number",
                  "minimum": 0,
                  "exclusiveMinimum": true,
                  "maximum": 1000000,
                  "description": "Portal sales units, at most 6 decimal places"
                },
                "unitPriceGross": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 1000000,
                  "description": "EUR gross price per portal sales unit; stored for review, not invoiced"
                },
                "vatPercent": {
                  "type": "number",
                  "minimum": 0,
                  "maximum": 100
                }
              }
            }
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "shippingAddress": {
            "$ref": "#/components/schemas/CustomerAddress"
          },
          "requireConfirmation": {
            "type": "boolean",
            "default": false,
            "description": "False/default: create the native JTL order atomically with reception when mapped. True: create on /confirm or accepted event. Unmapped orders remain held."
          },
          "billingAddress": {
            "$ref": "#/components/schemas/CustomerAddress"
          }
        }
      },
      "Acceptance": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "externalOrderId": {
            "type": "string",
            "minLength": 1
          },
          "account": {
            "type": "string",
            "minLength": 1
          },
          "region": {
            "type": "string",
            "minLength": 1
          },
          "status": {
            "type": "string",
            "description": "Integration state includes submitted_jtl_sql after native creation. Separate from partner orderStatus."
          },
          "environment": {
            "type": "string",
            "enum": [
              "operations-inbox"
            ]
          },
          "jtlSubmitted": {
            "type": "boolean",
            "description": "True after the native JTL order commits. False while waiting for confirmation or product mappings."
          },
          "reservationApplied": {
            "type": "boolean",
            "description": "True after native stock reservation postconditions pass. Physical stock is unchanged by order creation; valid native cancellation releases reservations."
          },
          "acceptedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "replayed": {
            "type": "boolean"
          },
          "resolvedLines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "externalLineId": {
                  "type": "string",
                  "minLength": 1
                },
                "externalSku": {
                  "type": "string",
                  "minLength": 1
                },
                "articleId": {
                  "type": "string",
                  "minLength": 1
                },
                "erpSku": {
                  "type": "string"
                },
                "erpQuantity": {
                  "type": "number"
                },
                "factor": {
                  "type": "number"
                },
                "mappingRevision": {
                  "type": "integer"
                }
              }
            }
          },
          "issues": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "sku": {
                  "type": "string",
                  "minLength": 1
                },
                "code": {
                  "type": "string",
                  "enum": [
                    "unmapped_sku",
                    "order_owner_not_stock_platform"
                  ]
                }
              }
            }
          },
          "processingError": {
            "type": "string",
            "nullable": true
          },
          "websiteConfirmed": {
            "type": "boolean"
          },
          "confirmationId": {
            "type": "string"
          },
          "confirmedAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "orderStatus": {
            "type": "string",
            "enum": [
              "received",
              "accepted",
              "rejected",
              "cancelled",
              "processing",
              "shipped",
              "delivered",
              "completed"
            ]
          },
          "version": {
            "type": "integer",
            "minimum": 1
          },
          "closed": {
            "type": "boolean"
          },
          "allowedTransitions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "shipments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "shipmentId",
                "carrier"
              ],
              "properties": {
                "shipmentId": {
                  "type": "string",
                  "maxLength": 100
                },
                "carrier": {
                  "type": "string",
                  "maxLength": 100
                },
                "trackingNumber": {
                  "type": "string",
                  "maxLength": 150
                },
                "trackingUrl": {
                  "type": "string",
                  "maxLength": 1000,
                  "description": "Optional absolute HTTPS URL without credentials"
                }
              }
            }
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "eventId": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "atUtc": {
                  "type": "string",
                  "format": "date-time"
                },
                "reason": {
                  "type": "string"
                },
                "version": {
                  "type": "integer"
                }
              }
            }
          },
          "jtl": {
            "type": "object",
            "description": "Fresh read on GET individual order. Never inferred from partner delivery events.",
            "properties": {
              "linked": {
                "type": "boolean"
              },
              "status": {
                "type": "string",
                "enum": [
                  "not_linked",
                  "not_found",
                  "reference_mismatch",
                  "source_unavailable",
                  "open",
                  "dispatched",
                  "cancelled"
                ]
              },
              "orderId": {
                "type": "integer"
              },
              "orderNumber": {
                "type": "string"
              },
              "cancelled": {
                "type": "boolean"
              },
              "fullyDispatched": {
                "type": "boolean"
              },
              "plannedCarrier": {
                "type": "string",
                "nullable": true
              },
              "shipments": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "observedAtUtc": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "billingAddress": {
            "$ref": "#/components/schemas/CustomerAddress"
          },
          "shippingAddress": {
            "$ref": "#/components/schemas/CustomerAddress"
          },
          "jtlCustomer": {
            "type": "object",
            "description": "Native customer linkage recorded on initial acceptance. created describes that acceptance, including when replayed. Older receipts may omit this object.",
            "properties": {
              "id": {
                "type": "integer"
              },
              "customerNumber": {
                "type": "string"
              },
              "created": {
                "type": "boolean"
              },
              "externalCustomerId": {
                "type": "string"
              }
            }
          },
          "jtlOrderId": {
            "type": "integer",
            "description": "Native JTL sales order ID, absent while held or waiting for confirmation."
          },
          "jtlOrderNumber": {
            "type": "string",
            "description": "BW-<receipt GUID> native number; standard JTL number counters are unchanged."
          },
          "jtlExternalOrderId": {
            "type": "string",
            "description": "BGW-<receipt GUID> scoped native reference. Website externalOrderId is stored as native customer order number."
          },
          "nativeOrderMode": {
            "type": "string",
            "description": "sql for new v1.9 receipts. Older receipts are not auto-imported."
          },
          "jtlImport": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "awaiting_confirmation",
                  "held_mapping",
                  "cancelled"
                ]
              },
              "adapter": {
                "type": "string",
                "enum": [
                  "transactional-sql"
                ]
              }
            }
          }
        }
      },
      "Shipments": {
        "type": "array",
        "minItems": 1,
        "maxItems": 50,
        "description": "Complete replacement of all partner-reported packages. trackingNumber is mandatory for every package when posting shipped.",
        "items": {
          "type": "object",
          "required": [
            "shipmentId",
            "carrier"
          ],
          "properties": {
            "shipmentId": {
              "type": "string",
              "maxLength": 100
            },
            "carrier": {
              "type": "string",
              "maxLength": 100
            },
            "trackingNumber": {
              "type": "string",
              "maxLength": 150
            },
            "trackingUrl": {
              "type": "string",
              "maxLength": 1000,
              "description": "Optional absolute HTTPS URL without credentials"
            }
          }
        }
      },
      "OrderDetails": {
        "type": "object",
        "properties": {
          "externalOrderId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "Integration state includes submitted_jtl_sql after native creation. Separate from partner orderStatus."
          },
          "orderStatus": {
            "type": "string",
            "enum": [
              "received",
              "accepted",
              "rejected",
              "cancelled",
              "processing",
              "shipped",
              "delivered",
              "completed"
            ]
          },
          "version": {
            "type": "integer",
            "minimum": 1
          },
          "closed": {
            "type": "boolean"
          },
          "allowedTransitions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "shipments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "shipmentId",
                "carrier"
              ],
              "properties": {
                "shipmentId": {
                  "type": "string",
                  "maxLength": 100
                },
                "carrier": {
                  "type": "string",
                  "maxLength": 100
                },
                "trackingNumber": {
                  "type": "string",
                  "maxLength": 150
                },
                "trackingUrl": {
                  "type": "string",
                  "maxLength": 1000,
                  "description": "Optional absolute HTTPS URL without credentials"
                }
              }
            }
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "eventId": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "atUtc": {
                  "type": "string",
                  "format": "date-time"
                },
                "reason": {
                  "type": "string"
                },
                "version": {
                  "type": "integer"
                }
              }
            }
          },
          "jtl": {
            "type": "object",
            "description": "Fresh read on GET individual order. Never inferred from partner delivery events.",
            "properties": {
              "linked": {
                "type": "boolean"
              },
              "status": {
                "type": "string",
                "enum": [
                  "not_linked",
                  "not_found",
                  "reference_mismatch",
                  "source_unavailable",
                  "open",
                  "dispatched",
                  "cancelled"
                ]
              },
              "orderId": {
                "type": "integer"
              },
              "orderNumber": {
                "type": "string"
              },
              "cancelled": {
                "type": "boolean"
              },
              "fullyDispatched": {
                "type": "boolean"
              },
              "plannedCarrier": {
                "type": "string",
                "nullable": true
              },
              "shipments": {
                "type": "array",
                "items": {
                  "type": "object"
                }
              },
              "observedAtUtc": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "customer": {
            "$ref": "#/components/schemas/Customer"
          },
          "billingAddress": {
            "$ref": "#/components/schemas/CustomerAddress"
          },
          "shippingAddress": {
            "$ref": "#/components/schemas/CustomerAddress"
          },
          "jtlCustomer": {
            "type": "object",
            "description": "Native customer linkage recorded on initial acceptance. created describes that acceptance, including when replayed. Older receipts may omit this object.",
            "properties": {
              "id": {
                "type": "integer"
              },
              "customerNumber": {
                "type": "string"
              },
              "created": {
                "type": "boolean"
              },
              "externalCustomerId": {
                "type": "string"
              }
            }
          },
          "jtlOrderId": {
            "type": "integer",
            "description": "Native JTL sales order ID, absent while held or waiting for confirmation."
          },
          "jtlOrderNumber": {
            "type": "string",
            "description": "BW-<receipt GUID> native number; standard JTL number counters are unchanged."
          },
          "jtlExternalOrderId": {
            "type": "string",
            "description": "BGW-<receipt GUID> scoped native reference. Website externalOrderId is stored as native customer order number."
          },
          "nativeOrderMode": {
            "type": "string",
            "description": "sql for new v1.9 receipts. Older receipts are not auto-imported."
          },
          "jtlImport": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "awaiting_confirmation",
                  "held_mapping",
                  "cancelled"
                ]
              },
              "adapter": {
                "type": "string",
                "enum": [
                  "transactional-sql"
                ]
              }
            }
          },
          "jtlSubmitted": {
            "type": "boolean",
            "description": "True after the native JTL order commits. False while waiting for confirmation or product mappings."
          },
          "reservationApplied": {
            "type": "boolean",
            "description": "True after native stock reservation postconditions pass. Physical stock is unchanged by order creation; valid native cancellation releases reservations."
          }
        }
      },
      "Customer": {
        "type": "object",
        "required": [
          "externalCustomerId",
          "firstName",
          "lastName",
          "email",
          "phone"
        ],
        "properties": {
          "externalCustomerId": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "description": "Stable, case-sensitive website identity scoped by API account and region, also required for guests. Reuse this ID for returning customers. Do not use an order ID. Email alone never merges customers. Existing mappings reuse JTL master data; new order snapshots do not overwrite it."
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "minLength": 1,
            "format": "email"
          },
          "phone": {
            "type": "string",
            "maxLength": 30,
            "minLength": 1
          },
          "companyName": {
            "type": "string",
            "maxLength": 128
          },
          "vatId": {
            "type": "string",
            "maxLength": 20
          }
        }
      },
      "CustomerAddress": {
        "type": "object",
        "required": [
          "firstName",
          "lastName",
          "street",
          "postalCode",
          "city",
          "countryCode"
        ],
        "properties": {
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "street": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "postalCode": {
            "type": "string",
            "maxLength": 20,
            "minLength": 1
          },
          "city": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1
          },
          "countryCode": {
            "type": "string",
            "maxLength": 2,
            "minLength": 1,
            "pattern": "^[A-Z]{2}$"
          },
          "companyName": {
            "type": "string",
            "maxLength": 128
          },
          "addressLine2": {
            "type": "string",
            "maxLength": 200
          },
          "state": {
            "type": "string",
            "maxLength": 100
          }
        }
      },
      "SavedCustomerReference": {
        "type": "object",
        "minProperties": 1,
        "required": [
          "externalCustomerId"
        ],
        "properties": {
          "externalCustomerId": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "description": "Stable, case-sensitive website identity scoped by API account and region, also required for guests. Reuse this ID for returning customers. Do not use an order ID. Email alone never merges customers. Existing mappings reuse JTL master data; new order snapshots do not overwrite it."
          },
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "minLength": 1,
            "format": "email"
          },
          "phone": {
            "type": "string",
            "maxLength": 30,
            "minLength": 1
          },
          "companyName": {
            "type": "string",
            "maxLength": 128
          },
          "vatId": {
            "type": "string",
            "maxLength": 20
          }
        },
        "description": "Optional on order updates. All supplied fields must equal the original order snapshot exactly; externalCustomerId is required if customer is supplied. No customer creation or profile update occurs."
      },
      "SavedAddressReference": {
        "type": "object",
        "minProperties": 1,
        "properties": {
          "firstName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "lastName": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1
          },
          "street": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1
          },
          "postalCode": {
            "type": "string",
            "maxLength": 20,
            "minLength": 1
          },
          "city": {
            "type": "string",
            "maxLength": 64,
            "minLength": 1
          },
          "countryCode": {
            "type": "string",
            "maxLength": 2,
            "minLength": 1,
            "pattern": "^[A-Z]{2}$"
          },
          "companyName": {
            "type": "string",
            "maxLength": 128
          },
          "addressLine2": {
            "type": "string",
            "maxLength": 200
          },
          "state": {
            "type": "string",
            "maxLength": 100
          }
        },
        "description": "Optional verification of saved address fields. Every supplied field must equal the order snapshot. Does not edit the address."
      },
      "MailRequest": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "to",
          "subject"
        ],
        "properties": {
          "to": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "uniqueItems": true,
            "items": {
              "type": "string",
              "format": "email",
              "maxLength": 254
            },
            "description": "Plain addresses, no display names. Duplicates rejected case-insensitively."
          },
          "subject": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "No header control characters."
          },
          "textBody": {
            "type": "string",
            "maxLength": 20000
          },
          "htmlBody": {
            "type": "string",
            "maxLength": 40000
          },
          "replyTo": {
            "type": "string",
            "format": "email",
            "maxLength": 254
          }
        },
        "description": "At least one non-blank textBody or htmlBody is required. Request limit 64 KiB. from, attachments, cc and bcc are unsupported. Sender cannot be overridden."
      },
      "MailStatus": {
        "type": "object",
        "properties": {
          "mailId": {
            "type": "string",
            "format": "uuid"
          },
          "from": {
            "type": "string",
            "enum": [
              "info@bertigraf.nl"
            ]
          },
          "subject": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "waiting_configuration",
              "queued",
              "preparing",
              "submitting",
              "smtp_accepted",
              "delivered",
              "bounced",
              "delayed",
              "failed",
              "unknown",
              "mixed"
            ]
          },
          "createdAtUtc": {
            "type": "string",
            "format": "date-time"
          },
          "replayed": {
            "type": "boolean"
          },
          "smtpConfigured": {
            "type": "boolean",
            "description": "Configuration is enabled and a password is present. Not a live connection test."
          },
          "deliveryTrackingEnabled": {
            "type": "boolean"
          },
          "deliveryConfirmed": {
            "type": "boolean",
            "description": "True only when every recipient has confirmed delivery evidence. SMTP acceptance alone leaves this false."
          },
          "statusUrl": {
            "type": "string"
          },
          "recipients": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "recipientId": {
                  "type": "string",
                  "format": "uuid"
                },
                "to": {
                  "type": "string",
                  "format": "email"
                },
                "messageId": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "waiting_configuration",
                    "queued",
                    "preparing",
                    "submitting",
                    "smtp_accepted",
                    "delivered",
                    "bounced",
                    "delayed",
                    "failed",
                    "unknown"
                  ]
                },
                "attempts": {
                  "type": "integer"
                },
                "statusCode": {
                  "type": "string",
                  "nullable": true
                },
                "deliveryEvidence": {
                  "type": "string",
                  "nullable": true,
                  "enum": [
                    "mailbox_receipt",
                    "delivery_report",
                    null
                  ]
                },
                "smtpAcceptedAtUtc": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "deliveryReportAtUtc": {
                  "type": "string",
                  "format": "date-time",
                  "nullable": true
                },
                "updatedAtUtc": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          },
          "events": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "recipientId": {
                  "type": "string",
                  "format": "uuid"
                },
                "status": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "nullable": true
                },
                "atUtc": {
                  "type": "string",
                  "format": "date-time"
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/api/v1/orders": {
      "post": {
        "operationId": "acceptOrder",
        "summary": "Persist an order into the local review inbox",
        "description": "Currently disabled for NL.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderRequest"
              }
            }
          }
        },
        "responses": {
          "503": {
            "description": "NL order creation or confirmation is disabled."
          }
        }
      },
      "get": {
        "summary": "List account orders",
        "description": "Recent Operations receipts, newest first. Use individual GET for fresh JTL status.",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "in": "query",
            "name": "offset",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "maximum": 1000000,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated items with orderStatus, version and closed flag"
          },
          "401": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "422": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          }
        }
      }
    },
    "/api/v1/orders/{externalOrderId}": {
      "get": {
        "operationId": "getAcceptance",
        "summary": "Read acceptance status within token scope",
        "parameters": [
          {
            "name": "externalOrderId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Current order state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderDetails"
                }
              }
            }
          },
          "401": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "403": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "404": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "409": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "422": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "503": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          }
        }
      }
    },
    "/api/v1/mappings": {
      "get": {
        "operationId": "getMappings",
        "summary": "Read only this token account/country website SKU routes",
        "responses": {
          "200": {
            "description": "items contains externalSku, articleId, optional erpSku, factor, buffer, unit, orderOwner and revision; route drafts are lab-only",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "environment": {
                      "type": "string"
                    },
                    "account": {
                      "type": "string",
                      "minLength": 1
                    },
                    "region": {
                      "type": "string",
                      "minLength": 1
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "JSON error; correct the request or credentials before retrying",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "JSON error; correct the request or credentials before retrying",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "422": {
            "description": "JSON error; correct the request or credentials before retrying",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "JSON error; correct the request or credentials before retrying",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/orders/{externalOrderId}/confirm": {
      "post": {
        "summary": "Persist website order confirmation; idempotent by order and confirmationId",
        "description": "Currently disabled for NL.",
        "parameters": [
          {
            "in": "path",
            "name": "externalOrderId",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "confirmationId"
                ],
                "properties": {
                  "confirmationId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "customer": {
                    "$ref": "#/components/schemas/SavedCustomerReference"
                  },
                  "billingAddress": {
                    "$ref": "#/components/schemas/SavedAddressReference"
                  },
                  "shippingAddress": {
                    "$ref": "#/components/schemas/SavedAddressReference"
                  }
                }
              },
              "example": {
                "confirmationId": "WEB-CONFIRM-10001"
              }
            }
          }
        },
        "responses": {
          "503": {
            "description": "NL order creation or confirmation is disabled."
          }
        }
      }
    },
    "/api/v1/website/inventory": {
      "get": {
        "summary": "Combined stock of exactly JTL NL warehouse 12 by website product ID",
        "description": "productId = mapping listingId; SKU = externalSku. Combined physical stock excludes blocked stock and deducts aggregate open-order reservations conservatively, capped by JTL global availability. BOM components are scoped to the same warehouse IDs. Then quantity=floor(max(0,available-buffer)/factor). Missing source returns quantity null. Additional warehouses never enter the physical pool. A new warehouse can affect global reservations/availability caps but cannot add sellable stock here.",
        "parameters": [
          {
            "in": "query",
            "name": "productId",
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Mapped product IDs and combined stock availability from NL warehouse ID 12.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "environment": {
                      "type": "string"
                    },
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "productId": {
                            "type": "string"
                          },
                          "sku": {
                            "type": "string"
                          },
                          "articleId": {
                            "type": "string"
                          },
                          "factor": {
                            "type": "string"
                          },
                          "mappingRevision": {
                            "type": "integer"
                          },
                          "quantity": {
                            "type": "number",
                            "nullable": true
                          },
                          "status": {
                            "type": "string"
                          },
                          "warehouseIds": {
                            "type": "array",
                            "items": {
                              "type": "integer"
                            },
                            "example": [
                              1,
                              11
                            ]
                          },
                          "observedAtUtc": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "JSON error; correct credentials, identity or request before retrying."
          },
          "403": {
            "description": "JSON error; correct credentials, identity or request before retrying."
          },
          "404": {
            "description": "JSON error; correct credentials, identity or request before retrying."
          },
          "409": {
            "description": "JSON error; correct credentials, identity or request before retrying."
          },
          "422": {
            "description": "JSON error; correct credentials, identity or request before retrying."
          },
          "503": {
            "description": "JSON error; correct credentials, identity or request before retrying."
          }
        }
      }
    },
    "/api/v1/website/mappings": {
      "post": {
        "summary": "Save website product card JTL ID and ratio mapping",
        "description": "Account/region from Bearer key. Validates JTL article; variation parents rejected. revision=0 to create; use returned revision for updates. Duplicate product ID / stale revision returns 409. SKU identifies mapping within account/region; changing SKU creates a different mapping.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "productId",
                  "sku",
                  "articleId",
                  "factor",
                  "buffer",
                  "unit",
                  "revision"
                ],
                "properties": {
                  "productId": {
                    "type": "string",
                    "maxLength": 150
                  },
                  "sku": {
                    "type": "string",
                    "maxLength": 100
                  },
                  "articleId": {
                    "type": "string",
                    "description": "JTL kArtikel positive integer as string"
                  },
                  "factor": {
                    "type": "number",
                    "minimum": 0.000001,
                    "maximum": 1000000
                  },
                  "buffer": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1000000
                  },
                  "unit": {
                    "type": "string",
                    "enum": [
                      "qutu",
                      "m2",
                      "ədəd",
                      "ERP-vahidi"
                    ]
                  },
                  "revision": {
                    "type": "integer",
                    "minimum": 0
                  }
                }
              },
              "example": {
                "productId": "12345",
                "sku": "WEB-NAMUR",
                "articleId": "2391",
                "factor": 2.5,
                "buffer": 0,
                "unit": "qutu",
                "revision": 0
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Mapping including incremented revision"
          },
          "401": {
            "description": "Invalid key"
          },
          "409": {
            "description": "Stale revision or product ID conflict"
          },
          "422": {
            "description": "Invalid mapping or JTL article"
          },
          "503": {
            "description": "Source unavailable"
          }
        }
      }
    },
    "/api/v1/orders/{externalOrderId}/events": {
      "post": {
        "summary": "Apply a versioned acceptance, rejection, cancellation or fulfilment event",
        "description": "Uses the saved customer and order snapshots. New v1.9 receipts create native JTL orders through SQL when confirmation/mapping conditions allow. Customer, receipt, native order, BOM positions, totals and reservations commit or roll back together. Invalid native structure or insufficient stock returns 422 without partial writes. Rejection/cancellation of API-created native orders follows JTL cancellation validation. Shipping/delivery events do not create native delivery notes or physical stock movements.",
        "parameters": [
          {
            "in": "path",
            "name": "externalOrderId",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "eventId",
                  "status",
                  "expectedVersion"
                ],
                "properties": {
                  "eventId": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 100
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "received",
                      "accepted",
                      "rejected",
                      "cancelled",
                      "processing",
                      "shipped",
                      "delivered",
                      "completed"
                    ]
                  },
                  "expectedVersion": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 1000,
                    "description": "Required for rejected/cancelled"
                  },
                  "shipments": {
                    "$ref": "#/components/schemas/Shipments"
                  },
                  "customer": {
                    "$ref": "#/components/schemas/SavedCustomerReference"
                  },
                  "billingAddress": {
                    "$ref": "#/components/schemas/SavedAddressReference"
                  },
                  "shippingAddress": {
                    "$ref": "#/components/schemas/SavedAddressReference"
                  }
                }
              },
              "example": {
                "eventId": "ship-10001",
                "status": "shipped",
                "expectedVersion": 3,
                "shipments": [
                  {
                    "shipmentId": "parcel-1",
                    "carrier": "DHL",
                    "trackingNumber": "1234567890"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current order state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderDetails"
                }
              }
            }
          },
          "401": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "403": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "404": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "409": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "422": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "503": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          }
        }
      }
    },
    "/api/v1/orders/{externalOrderId}/jtl-link": {
      "post": {
        "summary": "Link a native JTL order for read-only tracking",
        "description": "Exact cExterneAuftragsnummer match required; one native order can only link once across all accounts. Linking is explicit, never guessed from order numbers, and increments version. Closed orders cannot be linked. Does not create or update JTL sales data. Uses the customer, both address snapshots and native JTL customer identity already saved with this order. No need to resend them. Optional customer/address fields verify the saved snapshot: mismatch 409, malformed reference 422. jtlCustomer is read-only. Successful responses include the stored customer context. Legacy snapshots are restored from original payload when available; missing legacy data is never invented.",
        "parameters": [
          {
            "in": "path",
            "name": "externalOrderId",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jtlOrderId",
                  "expectedVersion"
                ],
                "properties": {
                  "jtlOrderId": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "expectedVersion": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "customer": {
                    "$ref": "#/components/schemas/SavedCustomerReference"
                  },
                  "billingAddress": {
                    "$ref": "#/components/schemas/SavedAddressReference"
                  },
                  "shippingAddress": {
                    "$ref": "#/components/schemas/SavedAddressReference"
                  }
                }
              },
              "example": {
                "jtlOrderId": 12345,
                "expectedVersion": 1
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Current order state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderDetails"
                }
              }
            }
          },
          "401": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "403": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "404": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "409": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "422": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          },
          "503": {
            "description": "JSON error: 401 invalid key; 403 scope mismatch; 404 not found; 409 stale version, invalid transition or closed order; 422 invalid payload; 503 source unavailable."
          }
        }
      }
    },
    "/api/v1/auth/token": {
      "post": {
        "summary": "Obtain a one-hour access token using a one-time approved client grant",
        "security": [],
        "description": "Server-to-server client credentials exchange. The client grant remains valid until explicitly revoked; no new human approval is needed for each token. One issuance per client per 15 seconds; global limit 30 requests/minute. Respect Retry-After on 429. Older live tokens for that client retain at most five minutes, never extending their original expiry. Revocation blocks both issuance and existing client-issued tokens immediately. Cache and renew about two minutes before expiry. Legacy Bearer keys cannot self-approve clients. No automatic grant creation is exposed.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials"
                    ]
                  },
                  "client_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "client_secret": {
                    "type": "string",
                    "minLength": 32,
                    "maxLength": 256,
                    "writeOnly": true
                  }
                }
              },
              "example": {
                "grant_type": "client_credentials",
                "client_id": "YOUR_APPROVED_CLIENT_ID",
                "client_secret": "YOUR_PRIVATE_CLIENT_SECRET"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ],
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials"
                    ]
                  },
                  "client_id": {
                    "type": "string",
                    "format": "uuid"
                  },
                  "client_secret": {
                    "type": "string",
                    "minLength": 32,
                    "maxLength": 256,
                    "writeOnly": true
                  }
                }
              },
              "example": {
                "grant_type": "client_credentials",
                "client_id": "YOUR_APPROVED_CLIENT_ID",
                "client_secret": "YOUR_PRIVATE_CLIENT_SECRET"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Private, no-store token response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "access_token",
                    "token_type",
                    "expires_in",
                    "expires_at",
                    "client_id",
                    "account",
                    "region"
                  ],
                  "properties": {
                    "access_token": {
                      "type": "string"
                    },
                    "token_type": {
                      "type": "string",
                      "enum": [
                        "Bearer"
                      ]
                    },
                    "expires_in": {
                      "type": "integer",
                      "example": 3600
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "client_id": {
                      "type": "string",
                      "format": "uuid"
                    },
                    "account": {
                      "type": "string"
                    },
                    "region": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Unsupported grant type"
          },
          "401": {
            "description": "Invalid or revoked client credentials"
          },
          "415": {
            "description": "Use JSON or form encoding"
          },
          "429": {
            "description": "Issuance rate limit; honor Retry-After",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                },
                "description": "Seconds to wait"
              }
            }
          },
          "503": {
            "description": "Service unavailable; retry with backoff"
          }
        }
      }
    },
    "/api/v1/mail/messages": {
      "post": {
        "tags": [
          "Mail"
        ],
        "summary": "Queue a website email",
        "description": "Bearer authentication plus separate mail approval. Returns 202 and a mailId after durable storage, before SMTP delivery. Same Idempotency-Key and normalized unchanged content replay with 200 and the same mailId; changed content returns 409. Up to 30 new messages/minute and 100 recipient deliveries/rolling 24h per account by default. Each recipient has its own SMTP transaction and stable Message-ID. Ambiguous submission outcomes become unknown and are not automatically resent. Credentials and mail IDs are scoped to the account/region.",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 128
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MailRequest"
              },
              "example": {
                "to": [
                  "info@bertigraf.nl"
                ],
                "subject": "Website contact request",
                "textBody": "A customer has submitted a website enquiry.",
                "replyTo": "customer@example.com"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Durable mail status. Bodies and SMTP credentials are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailStatus"
                }
              }
            }
          },
          "202": {
            "description": "Durable mail status. Bodies and SMTP credentials are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailStatus"
                }
              }
            },
            "headers": {
              "Location": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Malformed JSON"
          },
          "401": {
            "description": "Invalid, expired or revoked Bearer token"
          },
          "403": {
            "description": "Account is not approved for mail"
          },
          "409": {
            "description": "Idempotency-Key reused for different content"
          },
          "413": {
            "description": "Request exceeds 64 KiB"
          },
          "415": {
            "description": "JSON Content-Type required"
          },
          "422": {
            "description": "Invalid mail content or missing Idempotency-Key"
          },
          "429": {
            "description": "Account quota reached; Retry-After header supplied"
          },
          "503": {
            "description": "Queue storage unavailable; retry unchanged request"
          }
        }
      }
    },
    "/api/v1/mail/messages/{mailId}": {
      "get": {
        "tags": [
          "Mail"
        ],
        "summary": "Read email submission and delivery status",
        "description": "Use the same approved API account. A foreign or unknown ID returns 404. smtp_accepted means STRATO accepted the SMTP transaction; it does not prove inbox placement or reading. delivered requires a correlated positive delivery report or verified receipt in the authenticated info@bertigraf.nl INBOX. bounced/failed are failures, delayed is a delivery delay, unknown means SMTP acknowledgement was ambiguous. External recipients may never provide positive delivery evidence; such messages can remain smtp_accepted. No read/open tracking.",
        "parameters": [
          {
            "name": "mailId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Durable mail status. Bodies and SMTP credentials are never returned.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MailStatus"
                }
              }
            }
          },
          "401": {
            "description": "Invalid, expired or revoked Bearer token"
          },
          "403": {
            "description": "Account is not approved for mail"
          },
          "404": {
            "description": "Mail ID does not belong to this account or does not exist"
          }
        }
      }
    },
    "/api/v1/mail/status": {
      "get": {
        "summary": "Check the NL mail profile",
        "responses": {
          "200": {
            "description": "NL sender, SMTP configuration and delivery tracking status."
          },
          "401": {
            "description": "Authentication required."
          },
          "403": {
            "description": "The API account lacks NL mail permission."
          }
        }
      }
    }
  }
}