{
  "openapi": "3.0.3",
  "info": {
    "title": "BOCP REST API",
    "version": "1.8.0",
    "description": "BOCP ERP integration API. Read and write documents, products, payments, AWBs and more.\n\n**Note on list endpoints:** filters are passed as URL *path segments*, not query strings. The format is `/key:value/key2:value2/`, not `?key=value&key2=value2`. See [Conventions](./conventions/) for the full list of standard filters.",
    "contact": {
      "name": "BOCP Support",
      "url": "https://kb.bocp.eu/"
    },
    "license": {
      "name": "Proprietary"
    }
  },
  "servers": [
    {
      "url": "/app/rest/v1/{company_id}",
      "description": "Your BOCP account",
      "variables": {
        "company_id": {
          "default": "0",
          "description": "Your BOCP account ID (visible in account settings)."
        }
      }
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {"name": "Sales",                 "description": "Bonuri de vânzare (sale receipts). The document that moves stock out."},
    {"name": "Invoices",              "description": "Facturi fiscale (fiscal invoices)."},
    {"name": "Proformas",             "description": "Facturi proforme (proforma invoices)."},
    {"name": "Fiscal Receipts",       "description": "Bonuri fiscale (cash register receipts)."},
    {"name": "Payments",              "description": "Încasări și plăți (receipts and payments)."},
    {"name": "Offers",                "description": "Ofertă / comandă produse."},
    {"name": "Receptions (GRN)",      "description": "Facturi de recepționat (goods receipt notes)."},
    {"name": "Stock Transfers",       "description": "Bonuri de transfer între gestiuni."},
    {"name": "Consumptions",          "description": "Bonuri de consum."},
    {"name": "Inventory",             "description": "Inventarieri."},
    {"name": "Products",              "description": "Nomenclator produse — read, patch, delete, stocks, images."},
    {"name": "Product Web Categories","description": "Categorii web pentru produse."},
    {"name": "AWB",                   "description": "Air WayBill — attach couriers' AWBs to sales."},
    {"name": "Courier Pickup Points", "description": "Puncte de ridicare curier."},
    {"name": "Workmanager",           "description": "Fișe service, sesizări și cereri de trimitere în service."},
    {"name": "Pricelists",             "description": "Liste de prețuri — enumerate available pricelists and look up their IDs for use in the product list filter."},
    {"name": "Contacts",              "description": "Agenda de contacte / clienți."},
    {"name": "Subscriptions",         "description": "Șabloane de abonament (subscription templates)."},
    {"name": "Workpoints",            "description": "Puncte de lucru."},
    {"name": "Postcodes",             "description": "Cod poștal (Romania) lookup."},
    {"name": "Attachments",           "description": "Fișiere atașate — upload files and attach them to any document, read attachment metadata, delete attachments. Files are stored on the account's external FTP storage (Setări cont → Setări Avansate → External FTP storage), which must be configured."},
    {"name": "eCommerce Orders",      "description": "Order connector for eCommerce and Dropship integrations. Both connector types use this same endpoint and payload. See the Sending Orders guide for the full integration flow."}
  ],
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic Auth with the API user and password issued in the BOCP admin panel (Integrări → BOCP REST API)."
      }
    },
    "schemas": {
      "ResponseEnvelope": {
        "type": "object",
        "description": "All BOCP API responses share this envelope.",
        "properties": {
          "is_error":       {"type": "boolean"},
          "http_code":      {"type": "integer"},
          "data":           {"description": "Endpoint-specific payload (object, array, or null)."},
          "messages":       {"type": "array", "items": {"type": "string"}},
          "request_route":  {"type": "string"},
          "your_ip":        {"type": "string"},
          "your_user":      {"type": "string"},
          "your_method":    {"type": "string", "enum": ["GET","POST","PATCH","PUT","DELETE"]},
          "page":           {"type": "integer", "description": "Present on paginated list responses."},
          "has_more_pages": {"type": "boolean", "description": "Present on paginated list responses."}
        }
      },
      "ClientBlock": {
        "type": "object",
        "description": "Included on documents linked to a client.",
        "properties": {
          "client_id":     {"type": "integer"},
          "client_name":   {"type": "string"},
          "client_cui":    {"type": "string"},
          "client_reg_com":{"type": "string"}
        }
      },
      "PaymentRequest": {
        "type": "object",
        "required": ["amount", "payment_type"],
        "properties": {
          "amount":         {"type": "number", "format": "float", "description": "Amount paid. Must be greater than 0."},
          "payment_type":   {"type": "string", "description": "Payment method. See allowed values under `/payments/types/`.", "example": "bank_transfer"},
          "date":           {"type": "string", "format": "date", "description": "Payment date (YYYY-MM-DD). Defaults to today."},
          "currency":       {"type": "string", "description": "ISO currency code. Defaults to the sale's currency.", "example": "RON"},
          "exchange_rate":  {"type": "number", "description": "Rate against RON. Only needed if `currency` differs from RON and you want to override the automatic rate."},
          "bank_account_id":{"type": "integer", "description": "Internal BOCP bank account ID. Relevant for `bank_transfer`."},
          "mentions":       {"type": "string", "description": "Free-form note stored with the payment."},
          "external_ref":   {"type": "string", "description": "Your own reference (order ID, transaction ID). Appended to the payment note."}
        }
      },
      "PaymentResponse": {
        "type": "object",
        "properties": {
          "incasare_id": {"type": "integer"},
          "bv_id":       {"type": "integer"},
          "amount":      {"type": "number"},
          "currency":    {"type": "string"},
          "payment_type":{"type": "string"},
          "date":        {"type": "string", "format": "date"}
        }
      },
      "FiscalReceiptImportItem": {
        "type": "object",
        "required": ["pos_receipt_id", "bv_id", "status_id"],
        "properties": {
          "pos_receipt_id":       {"type": "integer", "description": "Unique receipt ID in your external POS system."},
          "bv_id":                {"type": "integer", "description": "BOCP sale (bon de vânzare) ID."},
          "status_id":            {"type": "integer", "enum": [0,1,2,3], "description": "0=cancelled, 1=not printed, 2=in process, 3=printed."},
          "bf_content":           {"type": "string", "description": "Raw text command sent to the fiscal printer."},
          "printer_response":     {"type": "string", "description": "Raw text response from the fiscal printer."},
          "print_confirmed_ts":   {"type": "string", "format": "date-time"},
          "bf_date":              {"type": "string", "format": "date"},
          "bon_nr":               {"type": "integer", "description": "Fiscal receipt number printed by the ECR."},
          "z_nr":                 {"type": "integer", "description": "Z-report number."},
          "paid_cash":            {"type": "number"},
          "paid_card":            {"type": "number"},
          "paid_foodstamp":       {"type": "number"},
          "paid_voucher":         {"type": "number"},
          "paid_by_voucher_code": {"type": "string"},
          "paid_modern_payment":  {"type": "number"},
          "paid_other_payment":   {"type": "number"},
          "reimport_existing_bf": {"type": "integer", "enum": [0,1], "description": "0 = do not reimport, 1 = reimport by pos_receipt_id."}
        }
      },
      "AWBItem": {
        "type": "object",
        "required": ["sale_id", "awb_nr", "awb_type"],
        "properties": {
          "sale_id":     {"type": "integer", "description": "BOCP sale (bon de vânzare) ID."},
          "awb_nr":      {"type": "string",  "description": "AWB tracking number."},
          "awb_type":    {"type": "string",  "description": "Courier code (e.g. `fancurier`, `innoship`, `dpd`, `gls`, `sameday`, `urgentcargus`)."},
          "awb_subtype": {"type": "string",  "description": "Sub-courier when `awb_type` is a broker (e.g. `innoship` + sub `fancurier`)."},
          "awb_url":     {"type": "string",  "format": "uri", "description": "URL to the AWB PDF or courier tracking page."}
        }
      },
      "SendInServiceRequest": {
        "type": "object",
        "required": ["item_code", "item_name", "client_rec_id", "client_addres_id", "defect_description"],
        "properties": {
          "item_code":                {"type": "string"},
          "item_series":              {"type": "string"},
          "item_name":                {"type": "string"},
          "client_rec_id":            {"type": "integer"},
          "client_addres_id":         {"type": "integer"},
          "defect_description":       {"type": "string"},
          "acquisition_invoice_nr":   {"type": "string"},
          "acquisition_invoice_date": {"type": "string", "format": "date"},
          "supplier_name":            {"type": "string"},
          "invoice_address_id":       {"type": "integer"},
          "aparat_producator_id":     {"type": "integer"},
          "aparat_serie_id":          {"type": "integer"},
          "aparat_model_id":          {"type": "integer"},
          "equipment_type_id":        {"type": "integer"},
          "mentions":                 {"type": "string"},
          "requested_pickup_date":    {"type": "string", "format": "date"},
          "from_b2b":                 {"type": "integer", "enum": [0,1], "description": "1 = triggers pickup AWB generation via B2B path."}
        }
      },
      "PostcodeLookup": {
        "type": "object",
        "properties": {
          "postcode": {"type": "string"}
        }
      },

      "ConnectorInvoiceAddress": {
        "type": "object",
        "required": ["city", "county", "country", "street"],
        "properties": {
          "city":      {"type": "string"},
          "county":    {"type": "string"},
          "country":   {"type": "string", "example": "Romania"},
          "zip":       {"type": "string", "description": "Postal code. May be blank — BOCP will attempt to resolve it."},
          "street":    {"type": "string", "description": "Street name. Put the full address here if you cannot split it into separate fields."},
          "number":    {"type": "string"},
          "building":  {"type": "string"},
          "stair":     {"type": "string"},
          "floor":     {"type": "string"},
          "apartment": {"type": "string"},
          "GLN":       {"type": "string"}
        }
      },

      "ConnectorDeliveryAddress": {
        "type": "object",
        "required": ["city", "county", "country", "street"],
        "properties": {
          "city":                   {"type": "string", "description": "City. For Bucharest, include the sector (e.g. `sector 3`)."},
          "county":                 {"type": "string"},
          "country":                {"type": "string", "example": "Romania"},
          "zip":                    {"type": "string"},
          "street":                 {"type": "string"},
          "number":                 {"type": "string"},
          "building":               {"type": "string"},
          "stair":                  {"type": "string"},
          "floor":                  {"type": "string"},
          "apartment":              {"type": "string"},
          "shipping_contact_name":  {"type": "string"},
          "shipping_contact_phone": {"type": "string"},
          "GLN":                    {"type": "string"}
        }
      },

      "ConnectorOrderClient": {
        "type": "object",
        "required": ["name", "invoice_address", "delivery_address"],
        "properties": {
          "name":                {"type": "string", "description": "Company name, or full name for individuals."},
          "representative":      {"type": "string", "description": "Contact person name when `name` is a company. May be blank."},
          "email":               {"type": "string"},
          "phone":               {"type": "string"},
          "vat_id":              {"type": "string", "description": "VAT / CUI number. Used to look up an existing BOCP contact by fiscal code."},
          "registration_number": {"type": "string", "description": "Trade register number."},
          "client_unique_id":    {"type": "integer", "description": "Partner-side client ID. Recommended — lets BOCP recognise returning clients and update their record."},
          "invoice_address":     {"$ref": "#/components/schemas/ConnectorInvoiceAddress"},
          "delivery_address":    {"$ref": "#/components/schemas/ConnectorDeliveryAddress"}
        }
      },

      "ConnectorOrderItem": {
        "type": "object",
        "required": ["type", "code", "item_name", "item_quantity", "item_price", "item_vat_percent", "item_price_with_vat", "line_value_with_vat"],
        "properties": {
          "type":                         {"type": "string", "enum": ["product", "discount", "service"], "description": "`product` — code must match a product in BOCP. `discount` — negative-price line. `service` — maps to a service code (`SHIPPING` for shipping, `SERVICE` for anything else)."},
          "code":                         {"type": "string", "description": "SKU / product or service code."},
          "ean":                          {"type": "string"},
          "item_name":                    {"type": "string"},
          "item_comments":                {"type": "string", "description": "Per-item notes, personalisation requests, etc."},
          "item_requires_customization":  {"type": "integer", "enum": [0, 1]},
          "item_quantity":                {"type": "number"},
          "item_price":                   {"type": "number", "description": "Unit price without VAT."},
          "item_vat_percent":             {"type": "number", "description": "VAT rate, e.g. `19`."},
          "item_price_with_vat":          {"type": "number"},
          "line_value_with_vat":          {"type": "number", "description": "`item_quantity × item_price_with_vat`."},
          "discounted_price_with_vat":    {"type": "number", "description": "Optional. Effective unit price after discount. Triggers discount application when less than `item_price_with_vat` and `line_discount_value_with_vat` > 0."},
          "line_discount_value_with_vat": {"type": "number", "description": "Optional. Total line discount (with VAT)."}
        }
      },

      "ConnectorOrderPayment": {
        "type": "object",
        "required": ["payment_method", "payment_status", "payment_date", "payment_amount", "payment_currency", "payment_reference"],
        "properties": {
          "payment_method":    {"type": "string", "enum": ["bank_transfer", "card", "paypal", "credit"]},
          "payment_status":    {"type": "string", "enum": ["confirmed", "pending", "refunded"]},
          "payment_date":      {"type": "string", "description": "Date-time of the payment, e.g. `2024-01-03 13:12:02`."},
          "payment_amount":    {"type": "number"},
          "payment_currency":  {"type": "string", "description": "ISO currency code, e.g. `RON`, `EUR`, `USD`."},
          "payment_reference": {"type": "string", "description": "Unique identifier per payment within this order. Prevents duplicate recording on PUT resend."},
          "payment_comments":  {"type": "string"}
        }
      },

      "ConnectorOrderRequest": {
        "type": "object",
        "required": ["order_unique_id", "order_status", "shipping_method", "order_currency_code", "client", "items"],
        "properties": {
          "order_unique_id":     {"type": "string", "description": "Partner-side order ID. **Must match the `{order_id}` URL segment.**"},
          "order_reference":     {"type": "string", "description": "Human-readable reference. May be blank."},
          "order_date":          {"type": "string", "format": "date", "example": "2024-01-01"},
          "order_updated_at":    {"type": "string", "description": "Last-modified timestamp of the order in your system. Used to update `last_changed_ts` in BOCP."},
          "order_status":        {"type": "string", "description": "Updateable. Free-text status from your system (e.g. `new`, `processing`, `shipped`)."},
          "order_cancelled":     {"type": "integer", "enum": [0, 1], "description": "Updateable. Set to `1` to cancel the order in BOCP."},
          "order_mentions":      {"type": "string"},
          "order_currency_code": {"type": "string", "description": "ISO currency code for all item prices. Must be configured in the BOCP account."},
          "shipping_method":     {"type": "string", "enum": ["courier", "post", "personal_pickup", "other"]},
          "cod_amount":          {"type": "number", "description": "Cash-on-delivery amount collected by the partner. Required for correct AWB generation when COD applies. Send `0` if no COD."},
          "cod_currency":        {"type": "string", "example": "RON"},
          "client":              {"$ref": "#/components/schemas/ConnectorOrderClient"},
          "items":               {"type": "array", "items": {"$ref": "#/components/schemas/ConnectorOrderItem"}},
          "payments":            {"type": "array", "items": {"$ref": "#/components/schemas/ConnectorOrderPayment"}, "description": "Payments forwarded through the partner. Omit or send empty if none yet — resend the full order when payment is confirmed. Only `confirmed` payments count toward `paid_amount` in BOCP."}
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid Basic Auth credentials.",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResponseEnvelope"}}}
      },
      "BadRequest": {
        "description": "Request is malformed or a required field is missing / invalid.",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResponseEnvelope"}}}
      },
      "NotFound": {
        "description": "The requested resource does not exist.",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResponseEnvelope"}}}
      },
      "SuccessEnvelope": {
        "description": "OK — response uses the shared envelope.",
        "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResponseEnvelope"}}}
      }
    }
  },
  "paths": {

    "/sales/list/": {
      "get": {
        "tags": ["Sales"],
        "summary": "List sale receipts (bonuri de vânzare)",
        "description": "Returns a paginated list of sale receipts. In BOCP the sale receipt is the document that moves stock out. It can be issued with or without an invoice, with or without a fiscal receipt.\n\n**Filters are passed as URL path segments.** Example: `/sales/list/year:2024/modifiedafter:2024-06-01%2000:00:00/page:2/`.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `skipcancelled`, `page`.\n\n**Include:** `items`, `transactions`, `invoiced`.\n\n**Exclude:** `added-by-api`, `invoiced`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },
    "/sales/{bv_id}/payment/": {
      "get": {
        "tags": ["Sales", "Payments"],
        "summary": "Payment schema for a sale",
        "description": "Returns the JSON schema and allowed payment types for creating a payment on the given sale. Useful for building your integration UI.",
        "parameters": [
          {"in": "path", "name": "bv_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP ID of the sale."}
        ],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "post": {
        "tags": ["Sales", "Payments"],
        "summary": "Register a payment on a sale",
        "description": "Records an incasso (or payment) against an existing sale receipt.\n\n**Notes:**\n- You can register multiple payments on the same sale (partial payments, installments).\n- `incasare_id` in the response identifies the payment inside `/payments/list/`.\n- `payment_type` accepts the string codes returned by `/payments/types/`.",
        "parameters": [
          {"in": "path", "name": "bv_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP ID of the sale."}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/PaymentRequest"},
              "example": {"amount": 250.0, "payment_type": "bank_transfer", "date": "2026-05-20", "currency": "RON", "mentions": "OP 1234 BT", "external_ref": "TXN-98765"}
            }
          }
        },
        "responses": {
          "200": {"description": "Payment registered.", "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/ResponseEnvelope"}, {"type": "object", "properties": {"data": {"$ref": "#/components/schemas/PaymentResponse"}}}]}}}},
          "400": {"description": "Missing/invalid fields, or the sale is cancelled.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResponseEnvelope"}}}},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/invoices/list/": {
      "get": {
        "tags": ["Invoices"],
        "summary": "List fiscal invoices",
        "description": "Returns a paginated list of fiscal invoices.\n\n**Filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `skipcancelled`, `documentnr`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/proformas/list/": {
      "get": {
        "tags": ["Proformas"],
        "summary": "List proforma invoices",
        "description": "Returns a paginated list of proforma invoices.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.\n\n**Custom filters:**\n- `for_abon_tpl_id` — proformas issued from a specific subscription template.\n\n**Include:** `payment_link`.\n\n**Exclude:** `cancelled` (exclude cancelled proformas).",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/fiscalreceipts/list/": {
      "get": {
        "tags": ["Fiscal Receipts"],
        "summary": "List fiscal receipts (bonuri fiscale)",
        "description": "Returns a paginated list of fiscal receipts issued from the ECR (electronic cash register).\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.\n\n**Custom filters:**\n- `bv_id` — receipts attached to one sale\n- `raport_z_nr` — receipts in one Z report\n- `pl_id` — receipts issued at one workpoint\n- `statie_id` — receipts from one cash register station\n- `status_id` — `0`=cancelled, `1`=not printed, `2`=in process, `3`=printed\n\n**Include:** `items`, `printer_responses`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/payments/list/": {
      "get": {
        "tags": ["Payments"],
        "summary": "List payments and receipts",
        "description": "Returns a paginated list of payments and receipts (încasări și plăți).\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.\n\n**Custom filter — `allocation_status`:**\n- `any` — all payments\n- `not_allocated` — only unallocated\n- `some_allocated` — has some allocation, not fully\n- `fully_allocated` — fully allocated\n- `has_allocated` — has at least one allocation\n\n**Include:** `allocations`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },
    "/payments/types/": {
      "get": {
        "tags": ["Payments"],
        "summary": "List payment methods",
        "description": "Enumerates the predefined payment method codes accepted by `payment_type` fields in other endpoints (e.g. `POST /sales/{bv_id}/payment/`). Includes `bank_transfer`, `cash`, `card`, `voucher`, `foodstamps`, `modern_payment`, and any custom types defined in your account.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },
    "/payments/fiscalreceipt/": {
      "get": {
        "tags": ["Payments", "Fiscal Receipts"],
        "summary": "Schema for POSTing fiscal receipts",
        "description": "Returns a plain-text schema showing the expected shape of the POST payload for attaching one or more fiscal receipts to sales.",
        "responses": {
          "200": {"description": "Schema description (text/html).", "content": {"text/html": {}}}
        }
      },
      "post": {
        "tags": ["Payments", "Fiscal Receipts"],
        "summary": "Attach fiscal receipts to sales (batch)",
        "description": "Registers one or more fiscal receipts and their payments against existing sales.\n\n**Effect on the sale:**\n- Deletes all existing payments (`incasari`) on the sale.\n- Creates new payments based on the fiscal receipt totals (cash, card, voucher, food stamps, modern, other).\n- If an invoice exists on the sale, it must have the same date as the fiscal receipt.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["receipts"],
                "properties": {
                  "receipts": {
                    "type": "array",
                    "items": {"$ref": "#/components/schemas/FiscalReceiptImportItem"}
                  }
                }
              },
              "example": {
                "receipts": [
                  {
                    "pos_receipt_id": 1,
                    "bv_id": 198,
                    "status_id": 3,
                    "bf_date": "2025-05-05",
                    "bon_nr": 145,
                    "z_nr": 2,
                    "paid_cash": 10.0,
                    "paid_card": 5.0,
                    "paid_foodstamp": 14.0,
                    "paid_voucher": 44.0,
                    "paid_modern_payment": 30.0,
                    "paid_other_payment": 0.0,
                    "print_confirmed_ts": "2025-05-05 12:00:00",
                    "reimport_existing_bf": 0
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/offers/list/": {
      "get": {
        "tags": ["Offers"],
        "summary": "List product orders / offers (comenzi produse)",
        "description": "Returns a paginated list of `comenzi_produse` — the offer / product order table.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },

    "/grn/list/": {
      "get": {
        "tags": ["Receptions (GRN)"],
        "summary": "List Goods Receipt Notes (recepții)",
        "description": "Returns a paginated list of GRN (recepții). Each row includes the linked client (supplier) block.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `skipcancelled`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },
    "/grn/add/": {
      "post": {
        "tags": ["Receptions (GRN)"],
        "summary": "Add a received invoice for processing",
        "description": "**Not fully implemented yet.** Reserved endpoint for pushing a received invoice into the receptions queue.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },

    "/stocktransfers/list/": {
      "get": {
        "tags": ["Stock Transfers"],
        "summary": "List stock transfer receipts (bonuri de transfer)",
        "description": "Returns a paginated list of inter-workpoint stock transfers.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },

    "/consumptions/list/": {
      "get": {
        "tags": ["Consumptions"],
        "summary": "List consumption receipts (bonuri de consum)",
        "description": "Returns a paginated list of consumption receipts.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },

    "/inventory/list/": {
      "get": {
        "tags": ["Inventory"],
        "summary": "List inventories (inventarieri)",
        "description": "Returns a paginated list of inventory documents.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },
    "/inventory/view/{inventory_id}/": {
      "get": {
        "tags": ["Inventory"],
        "summary": "View inventory contents",
        "description": "Returns the stock rows scoped to a single inventory document (workpoint and, if set, sub-management).",
        "parameters": [
          {"in": "path", "name": "inventory_id", "required": true, "schema": {"type": "integer"}}
        ],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/product/list/": {
      "get": {
        "tags": ["Products"],
        "summary": "List products (product variants)",
        "description": "Returns a paginated list of product variants from the catalogue.\n\n**Filters:**\n- Standard filters (`year`, `page`, `modifiedafter`, ...).\n- `format` — `json` (default), `xml`, `csv`.\n- `code` — product code or barcode (URL-encoded). Also `codeb64:...` (base64).\n- `manufacturer` — filter by manufacturer name.\n- `visibility` — `all` (default), `partner`, `published`.\n- `show_deleted` — `0` or `1`.\n- `magazia_id` — workpoint ID for stock context.\n- `cont` — `sellable` (default) or `all` (subgestiune scope).\n- `product_id` — BOCP variant ID.\n- `fm_id` — fișă de magazie ID.\n- `pricelist_id` — filter to only products that have a rule in this pricelist, and return pricelist prices. **Requires `magazia_id`.** Price fields in the response depend on the workpoint type: en-gros workpoints return the pricelist price in `pret_vanzare` / `pret_vanzare_cu_tva` (discounted fields become `null`); en-detail workpoints return the normal retail price in `pret_vanzare` and the pricelist price in `pret_vanzare_discounted` / `pret_vanzare_cu_tva_discounted`. Use `/pricelists/list/` to enumerate available pricelist IDs.\n\n**Include:** `images`, `properties`, `last_aquisition_price_ron`, `last_purchase_price_currency`.\n\n**Exclude:** `deleted`, `archived`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },
    "/product/stocks/": {
      "get": {
        "tags": ["Products"],
        "summary": "List product stocks",
        "description": "Same filter set as `/product/list/`, but the response focus is on stock levels rather than catalogue metadata.\n\n**Include:** `last_purchase_price_currency`, `onstock`, `aquisition_value_ron`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },
    "/product/{product_id}/": {
      "get": {
        "tags": ["Products"],
        "summary": "Get product variant details",
        "description": "Returns the full detail record for one product variant.",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "patch": {
        "tags": ["Products"],
        "summary": "Partial update on a product variant",
        "description": "Send only the fields you want to change. Field names match those returned by the GET call. Combined fields (e.g. price + currency) must be sent together.",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "requestBody": {
          "required": true,
          "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "put": {
        "tags": ["Products"],
        "summary": "Replace product variant fields",
        "description": "PUT variant of the product update. Prefer PATCH for partial updates.",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "requestBody": {
          "required": true,
          "content": {"application/json": {"schema": {"type": "object", "additionalProperties": true}}}
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "delete": {
        "tags": ["Products"],
        "summary": "Delete a product variant",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/product/{product_id}/images/": {
      "get": {
        "tags": ["Products"],
        "summary": "List attached images for a product",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "delete": {
        "tags": ["Products"],
        "summary": "Delete ALL images from a product",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/product/{product_id}/image/": {
      "post": {
        "tags": ["Products"],
        "summary": "Attach an image from an uploaded file",
        "description": "Uploads and attaches a new image to the product. Multipart form-data body.\n\nFields: `file` (binary), `title`, `description`, `alt_text`.",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file":        {"type": "string", "format": "binary"},
                  "title":       {"type": "string"},
                  "description": {"type": "string"},
                  "alt_text":    {"type": "string"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "put": {
        "tags": ["Products"],
        "summary": "Attach an image from a URL",
        "description": "Attaches an image to the product from a remote URL. Depending on the catalog setting the URL is either stored as-is or downloaded to the local CDN.",
        "parameters": [{"in": "path", "name": "product_id", "required": true, "schema": {"type": "integer"}}],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["URL"],
                "properties": {
                  "URL":         {"type": "string", "format": "uri"},
                  "title":       {"type": "string"},
                  "description": {"type": "string"},
                  "alt_text":    {"type": "string"}
                }
              },
              "example": {"URL": "https://example.com/img.jpg", "title": "Hero", "alt_text": "Product front"}
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },
    "/product/add/": {
      "post": {
        "tags": ["Products"],
        "summary": "Create a new product (not implemented yet)",
        "description": "Reserved. This endpoint is documented for planning purposes; it is not implemented in the current release.",
        "responses": {
          "501": {"description": "Not implemented."}
        }
      }
    },

    "/productwebcategories/{category_id}/image/": {
      "post": {
        "tags": ["Product Web Categories"],
        "summary": "Attach/replace an image on a web category",
        "description": "Uploads an image and sets it on the given web category. Replaces the existing image if present.\n\nFields: `file` (binary), `title`, `description`, `alt_text`.",
        "parameters": [{"in": "path", "name": "category_id", "required": true, "schema": {"type": "integer"}}],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file"],
                "properties": {
                  "file":        {"type": "string", "format": "binary"},
                  "title":       {"type": "string"},
                  "description": {"type": "string"},
                  "alt_text":    {"type": "string"}
                }
              }
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/productwebcategories/{category_id}/": {
      "delete": {
        "tags": ["Product Web Categories"],
        "summary": "Delete a web category",
        "parameters": [{"in": "path", "name": "category_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/awb/list/": {
      "get": {
        "tags": ["AWB"],
        "summary": "List AWBs",
        "description": "Returns a paginated list of AWBs.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.\n\n**Custom filter:**\n- `documentnr` — filter by AWB tracking number.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },
    "/awb/add/": {
      "get": {
        "tags": ["AWB"],
        "summary": "Sample body for POST /awb/add/",
        "description": "Returns a schema/example payload for the POST call and lists the accepted courier codes.",
        "responses": {"200": {"description": "Schema/example (text/html)."}}
      },
      "post": {
        "tags": ["AWB"],
        "summary": "Register / attach AWBs to sales (batch)",
        "description": "Attaches one or more already-issued AWBs to existing sales. Use when the AWB was generated in a third-party system (broker, courier portal) and you want BOCP to know about it.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["awbs"],
                "properties": {
                  "awbs": {"type": "array", "items": {"$ref": "#/components/schemas/AWBItem"}}
                }
              },
              "example": {
                "awbs": [
                  {"sale_id": 3, "awb_nr": "11111",  "awb_type": "fancurier", "awb_subtype": "",         "awb_url": "https://domain.ro/url"},
                  {"sale_id": 4, "awb_nr": "222222", "awb_type": "innoship",  "awb_subtype": "fancurier","awb_url": "https://domain.ro/url"}
                ]
              }
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },
    "/awb/{awb_id}/": {
      "delete": {
        "tags": ["AWB"],
        "summary": "Delete an AWB attached to a sale",
        "parameters": [{"in": "path", "name": "awb_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/awb/issue/": {
      "get": {
        "tags": ["AWB"],
        "summary": "Placeholder (not implemented)",
        "description": "Planned endpoint for issuing an AWB from BOCP based on a sale. Not implemented yet.",
        "responses": {"501": {"description": "Not implemented."}}
      }
    },

    "/courierpickuppoints/list/{courier_id}/": {
      "get": {
        "tags": ["Courier Pickup Points"],
        "summary": "List pickup points for one courier",
        "description": "Returns the imported pickup point directory for a specific courier connector (DPD, FanCourier, SameDay, etc.). Values are cached locally in BOCP; refreshed from the courier via internal jobs.",
        "parameters": [
          {"in": "path", "name": "courier_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP courier connector ID."}
        ],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },

    "/workmanager/servicefiles/": {
      "get": {
        "tags": ["Workmanager"],
        "summary": "List service files (fișe service)",
        "description": "Returns a paginated list of service files.\n\n**Standard filters:** `year`, `yearmonth`, `yearmonthday`, `date`, `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.\n\n**Include:** `clientrecommendations`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "401": {"description": "Workmanager module is not active in your account."}
        }
      }
    },
    "/workmanager/servicefile/{fisa_id}/": {
      "get": {
        "tags": ["Workmanager"],
        "summary": "View a single service file",
        "description": "Returns the full record for one service file, including its associated documents.",
        "parameters": [{"in": "path", "name": "fisa_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },
    "/workmanager/servicerequests/": {
      "get": {
        "tags": ["Workmanager"],
        "summary": "List service requests (sesizări)",
        "description": "Returns a paginated list of service requests raised by clients.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },
    "/workmanager/sendinrequest/": {
      "post": {
        "tags": ["Workmanager"],
        "summary": "Create a send-in service request",
        "description": "Creates a `cerere trimitere în service` — a client-side request to send an item in for service. Optionally triggers pickup AWB generation via the B2B path.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/SendInServiceRequest"},
              "example": {
                "item_code": "ABC123",
                "item_series": "ZXCV56789",
                "item_name": "TV Set",
                "client_rec_id": 1,
                "client_addres_id": 2,
                "defect_description": "Blank screen",
                "acquisition_invoice_nr": "FA23",
                "acquisition_invoice_date": "2024-01-01",
                "supplier_name": "SC Furnizor SRL",
                "invoice_address_id": -1,
                "aparat_producator_id": -1,
                "equipment_type_id": -1,
                "mentions": "Please pick up between 09:00-17:00",
                "requested_pickup_date": "2026-07-15",
                "from_b2b": 1
              }
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },

    "/contacts/list/": {
      "get": {
        "tags": ["Contacts"],
        "summary": "List clients",
        "description": "Returns a paginated list of clients (`agendă firme` rows where `is_client = 1`).\n\n**Standard filters:** `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page` (date filters use `lastupdated`).\n\n**Include:**\n- `pricelist` — adds `pricelist_id` and `pricelist_name` to each row. Falls back to the client's group pricelist when the contact has no direct pricelist.\n- `address` — adds `addresses[]` array (all non-deleted delivery/invoice addresses).\n- `contacts` — adds `contacts[]` array (linked contact persons).\n- `banks` — adds `bank_accounts[]` array (linked bank accounts with currency).\n\nExample: `/contacts/list/modifiedafter:2024-01-01/include:address,pricelist/`",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/contacts/{contact_id}/": {
      "get": {
        "tags": ["Contacts"],
        "summary": "Get contact details",
        "description": "Returns the details of one contact (`agendă firme` row), formatted using the shared client-data helper.\n\n**Include:**\n- `pricelist` — adds `pricelist_id` and `pricelist_name` to the response. Falls back to the client's group pricelist.",
        "parameters": [{"in": "path", "name": "contact_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/subscriptions/{abon_tpl_id}/": {
      "get": {
        "tags": ["Subscriptions"],
        "summary": "Get subscription template details",
        "description": "Returns the details of one subscription template: name, description, price with/without VAT, period in months, currency, unit, group, VAT %.",
        "parameters": [{"in": "path", "name": "abon_tpl_id", "required": true, "schema": {"type": "integer"}}],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/workpoints/list/": {
      "get": {
        "tags": ["Workpoints"],
        "summary": "List workpoints (puncte de lucru)",
        "description": "Returns all workpoints defined in the account.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"}
        }
      }
    },

    "/pricelists/list/": {
      "get": {
        "tags": ["Pricelists"],
        "summary": "List pricelists",
        "description": "Returns a paginated list of pricelists defined in the account.\n\nEach row includes: `bocp_id`, `name`, `deleted`, `item_count` (number of product rules), `currency`, `currency_id`, `flag_used_for_sites`, `last_changed_ts`, `rec_ts`.\n\nUse the returned `bocp_id` as the `pricelist_id` filter value on `/product/list/`.\n\n**Standard filters:** `modifiedafter`, `modifiedthrough`, `id`, `minid`, `page`.",
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/postcodes/": {
      "get": {
        "tags": ["Postcodes"],
        "summary": "Look up a Romanian postal code",
        "description": "Looks up a Romanian postal code by county / city / address. Filters are passed as URL path segments and are **all required** except `country`.\n\nIf the city is `Bucuresti`, the address should include the sector (e.g. `str Nume Nr 2 sector 3`).\n\nSpaces may be replaced with `_` or `-`. Diacritics are optional.",
        "parameters": [
          {"in": "path", "name": "country", "required": false, "schema": {"type": "string", "default": "Romania"}, "description": "Filter `country:Romania` (or `country:RO`). Defaults to Romania."},
          {"in": "path", "name": "county",  "required": true,  "schema": {"type": "string"}, "description": "Filter `county:...` — county name or short code (MS, B, CJ, CT, ...)."},
          {"in": "path", "name": "city",    "required": true,  "schema": {"type": "string"}, "description": "Filter `city:...` — city name."},
          {"in": "path", "name": "address", "required": true,  "schema": {"type": "string"}, "description": "Filter `address:...` — street name and number."}
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/ResponseEnvelope"}, {"type": "object", "properties": {"data": {"$ref": "#/components/schemas/PostcodeLookup"}}}]}}}
          },
          "400": {"$ref": "#/components/responses/BadRequest"}
        }
      }
    },

    "/attachments/upload/": {
      "post": {
        "tags": ["Attachments"],
        "summary": "Upload a file and attach it to a document",
        "description": "Uploads a file and attaches it to an existing BOCP document. Multipart form-data body.\n\nThe file is stored on the account's **external FTP storage** (Setări cont → Setări Avansate → External FTP storage); a `503` is returned when that storage is not configured or unreachable.\n\n**Notes:**\n- server-executable file extensions (php, cgi, sh, asp, ...) are rejected with `400`\n- large images are automatically resized and some document types are compressed by the storage layer — always download the file through its BOCP access URL, never directly from the FTP host\n- `GET /attachments/upload/` returns this documentation inline",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["file", "doctype", "doc_id"],
                "properties": {
                  "file":               {"type": "string", "format": "binary", "description": "The file to upload."},
                  "doctype":            {"type": "integer", "description": "BOCP doctype of the document to attach to (must be > 0)."},
                  "doc_id":             {"type": "integer", "description": "`bocp_id` of the document to attach to (must be > 0)."},
                  "client_id":          {"type": "integer", "description": "BOCP contact id the file belongs to. Default -1.", "default": -1},
                  "title":              {"type": "string",  "description": "Display title. Defaults to the sanitized file name."},
                  "is_public":          {"type": "integer", "enum": [0, 1], "description": "1 = public download link, 0 = logged-in access only. Default 1.", "default": 1},
                  "attachment_type_id": {"type": "integer", "description": "Attachment type id. Default -1.", "default": -1}
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK — `data.attached_file_rec_ids` holds the new attachment id(s).",
            "content": {"application/json": {"schema": {"allOf": [{"$ref": "#/components/schemas/ResponseEnvelope"}, {"type": "object", "properties": {"data": {"type": "object", "properties": {"attached_file_rec_ids": {"type": "array", "items": {"type": "integer"}}}}}}]}}}
          },
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "503": {"description": "The account's external FTP storage is not configured or is unreachable — retry later."}
        }
      }
    },

    "/attachments/{attachment_id}/": {
      "get": {
        "tags": ["Attachments"],
        "summary": "Attachment metadata",
        "description": "Returns the attachment's metadata: `bocp_id`, `doctype`, `doc_id`, `client_id`, `title`, `original_filename`, `filesize`, `is_public`, `attachment_type_id`, `uploaded_ts`.",
        "parameters": [
          {"in": "path", "name": "attachment_id", "required": true, "schema": {"type": "integer"}}
        ],
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      },
      "delete": {
        "tags": ["Attachments"],
        "summary": "Delete an attachment",
        "description": "Deletes the attachment: the file on the external FTP storage, the metadata row and its download secret key.\n\nWhen the external FTP storage is unreachable the attachment is kept and a `503` is returned — retry later. Requires the `DELETE` HTTP method to be allowed for your API user.",
        "parameters": [
          {"in": "path", "name": "attachment_id", "required": true, "schema": {"type": "integer"}}
        ],
        "responses": {
          "200": {"description": "Deleted — `data.deleted_attachment_id` confirms the removed id."},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"$ref": "#/components/responses/NotFound"},
          "503": {"description": "The account's external FTP storage is unreachable — the attachment was NOT deleted, retry later."}
        }
      }
    },

    "/connector/{connector_id}/order/list/": {
      "get": {
        "tags": ["eCommerce Orders"],
        "summary": "List orders",
        "description": "Returns a paginated list of orders imported through a connector.\n\n**Filters are passed as URL path segments.** Example: `/connector/7/order/list/id:123/page:2/`.\n\n**Supported filters:** `id`, `minid`, `page`.",
        "parameters": [
          {"in": "path", "name": "connector_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP connector ID."}
        ],
        "responses": {
          "200": {
            "description": "Paginated list of orders. Each item includes linked sales receipts, invoices, and AWBs.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {"$ref": "#/components/schemas/ResponseEnvelope"},
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rec_id":      {"type": "integer", "description": "BOCP internal order ID."},
                              "external_id": {"type": "string",  "description": "Partner-side order identifier."},
                              "status":      {"type": "string",  "description": "Current order status."},
                              "sales": {
                                "type": "array",
                                "description": "Sale receipts issued for this order.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "series": {"type": "string"},
                                    "nr":     {"type": "integer"},
                                    "date":   {"type": "string", "format": "date"}
                                  }
                                }
                              },
                              "invoices": {
                                "type": "array",
                                "description": "Fiscal invoices linked to the order.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "seria": {"type": "string"},
                                    "nr":    {"type": "integer"},
                                    "date":  {"type": "string", "format": "date"}
                                  }
                                }
                              },
                              "awbs": {
                                "type": "array",
                                "description": "AWBs generated for this order.",
                                "items": {
                                  "type": "object",
                                  "properties": {
                                    "nr_awb":  {"type": "string"},
                                    "pdf_url": {"type": "string", "format": "uri"}
                                  }
                                }
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      }
    },

    "/connector/{connector_id}/order/{order_id}/": {
      "post": {
        "tags": ["eCommerce Orders"],
        "summary": "Submit a new order",
        "description": "Creates a new order in BOCP from the connector. The `{order_id}` URL segment must equal `order_unique_id` in the body — a mismatch returns `400`.\n\n**DropShip vs eCommerce:** Both connector types use this same endpoint and payload. The difference is in invoice issuance: for DropShip connectors the invoice is issued to the dropshipper's preconfigured BOCP contact; for eCommerce connectors the invoice goes directly to the `client` supplied in the order.\n\n**Authentication:** HTTP Basic with credentials tied to this specific connector. The API user cannot access other connectors.\n\n**Webhook notifications (BOCP → partner):** BOCP POSTs JSON to your configured notification URL on three events. Your endpoint must return HTTP 200 to acknowledge; HTTP 4xx closes the notification permanently; any other code retries periodically.\n\n*Status change:* `{\"order_unique_id\":\"...\",\"notification_type\":\"status\",\"status_text\":\"...\"}`\n\n*Invoice issued:* `{\"notification_type\":\"invoice\",\"invoice_number\":\"...\",\"invoice_date\":\"...\",\"invoice_series\":\"...\",\"invoice_total\":0.0,\"invoice_currency\":\"RON\",\"invoice_total_RON\":0.0,\"invoice_url_html\":\"...\",\"invoice_url_pdf\":\"...\"}`\n\n*AWB issued:* `{\"notification_type\":\"awb\",\"awb_number\":\"...\",\"awb_packet_count\":1,\"weight\":1.5,\"shipping_payer\":\"...\",\"cash_on_delivery\":0.0,\"cash_on_delivery_currency\":\"RON\",\"delivery_country\":\"Romania\",\"declared_value\":0.0,\"awb_url_pdf\":\"...\"}`\n\n**Product push (same URL):** the identical notification URL also receives product-catalog pushes — BOCPRAPI is wired as a regular push destination (like Shopify, GoMag, ...), not a passive feed, so `GET .../products/` no longer needs to be polled on a schedule. Every push carries the CURRENT full product state (not a diff), using the same field names as `GET .../products/`. See the *Sending Orders* guide, section \"Product push (catalog changes)\", for the full field list.\n\n*New / republished:* `notification_type=\"product_published\"`. *Field, stock or price change:* `notification_type=\"product_update\"`. *Disconnected / deleted:* `notification_type=\"product_unpublished\"` — `{\"notification_type\":\"product_unpublished\",\"cod_produs\":\"...\"}`. Single packet per product for now.",
        "parameters": [
          {"in": "path", "name": "connector_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP connector ID assigned when the connector was created."},
          {"in": "path", "name": "order_id",     "required": true, "schema": {"type": "string"},  "description": "Partner-side order identifier. Must match `order_unique_id` in the body."}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/ConnectorOrderRequest"}
            }
          }
        },
        "responses": {
          "200": {"description": "Order accepted. Response `data` contains `BOCP_order_id`, plus `notices` and `errors` arrays.", "content": {"application/json": {"schema": {"$ref": "#/components/schemas/ResponseEnvelope"}}}},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"}
        }
      },
      "put": {
        "tags": ["eCommerce Orders"],
        "summary": "Update an existing order",
        "description": "Updates an order previously submitted via POST. All body fields are optional except `order_unique_id`. Only fields present in the body are updated:\n\n- `items` — if present, **fully replaces** all existing order items.\n- `client` — if present, updates client data.\n- `order_status`, `order_cancelled` — always updateable individually without sending the full payload.",
        "parameters": [
          {"in": "path", "name": "connector_id", "required": true, "schema": {"type": "integer"}},
          {"in": "path", "name": "order_id",     "required": true, "schema": {"type": "string"}}
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {"$ref": "#/components/schemas/ConnectorOrderRequest"}
            }
          }
        },
        "responses": {
          "200": {"$ref": "#/components/responses/SuccessEnvelope"},
          "400": {"$ref": "#/components/responses/BadRequest"},
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/connector/{connector_id}/products/": {
      "get": {
        "tags": ["eCommerce Orders"],
        "summary": "Read the product catalog (pull feed)",
        "description": "Returns the products published to this connector, so you can obtain the product `code` used in order `items` and keep stock/prices in sync.\n\n**Only products published to this connector** (from *Product Catalogue* in BOCP) are returned — there is no push; you pull the current list on demand. The response has the **same shape as `/product/list/`**, so a single parser handles both. Prices and stock reflect the connector's configured working point.\n\n**Filters are passed as URL path segments.** Example: `/connector/7/products/include:images/page:2/`.\n\n**Supported filters:** `page`, `format` (`json`|`xml`|`csv`), `code` (single product by code/barcode), `modifiedafter:YYYY-MM-DD HH:MM:SS` (incremental sync), `include:images,properties`, `magazia_id` (override pricing/stock working point).\n\nUse `cod_produs` from each item as the `code` when building orders.",
        "parameters": [
          {"in": "path", "name": "connector_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP connector ID assigned when the connector was created."}
        ],
        "responses": {
          "200": {
            "description": "Paginated list of the products published to this connector.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {"$ref": "#/components/schemas/ResponseEnvelope"},
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "product_id":           {"type": "integer", "description": "BOCP internal product variant ID."},
                              "cod_varianta":         {"type": "string",  "description": "BOCP variant group code."},
                              "cod_produs":           {"type": "string",  "description": "Product code — use as the order item `code`."},
                              "barcode":              {"type": "string"},
                              "product_name":         {"type": "string"},
                              "category_name":        {"type": "string"},
                              "stoc_global":          {"type": "number",  "description": "Total stock across all warehouses (not reduced by reservations)."},
                              "stock_reserved":       {"type": "number",  "description": "Quantity currently reserved for other orders, across all warehouses."},
                              "stock_available":      {"type": "number",  "description": "Sellable quantity: `stoc_global - stock_reserved`, floored at 0."},
                              "unitate_masura":       {"type": "string"},
                              "pret_vanzare":         {"type": "number",  "description": "Selling price without VAT."},
                              "pret_vanzare_cu_tva":  {"type": "number",  "description": "Selling price with VAT."},
                              "cota_tva_vanzare":     {"type": "number",  "description": "VAT percentage."},
                              "selling_currency_txt": {"type": "string"},
                              "website_published":    {"type": "integer"},
                              "producator":           {"type": "string"},
                              "last_changed_ts":      {"type": "string", "description": "Last modification timestamp — filter with `modifiedafter`."}
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    },

    "/connector/{connector_id}/config/": {
      "get": {
        "tags": ["eCommerce Orders"],
        "summary": "Read the connector configuration",
        "description": "Returns the connector's current configuration so your integration knows **what to expect** when it sends orders: which automations will run (sale issue, invoicing, proforma, AWB), how products sync in both directions, and how order events map to statuses.\n\n**Read-only** — a GET never changes any setting.\n\n**Option fields** are exported as an object with three keys so you can also see what values are possible:\n\n`{\"value\": \"a\", \"label\": \"AUTOMAT: ...\", \"options\": {\"a\": \"AUTOMAT: ...\", \"m\": \"MANUAL: ...\"}}`\n\nSettings that fall back to an account-wide default when not set per connector also carry a `source` key (`connector` or `global`).\n\n**Response groups (`data`):**\n\n- `connector` — id, site domain, type, active flag, order API URL, configured `order_event_notification_url`.\n- `products` — auto-import into the ERP (`autoimport_into_erp`, `import_mode`: `all` | `ordered_only` | `only_categories_with_autoimport`), update options (update product code in ERP, disconnect multiple connections, group products, fields updated from the site), the sub-inventory where new products are created, and `push` (product push active, `products_feed_url`, and `product_change_notify_url` — the same URL as `order_event_notification_url`; BOCP pushes the full current product state there on publish/update/unpublish, same as any other push destination).\n- `order_processing` — order confirmation method (automatic/manual), the working point where orders are auto-processed, action on order import (reserve stock / issue sale / ...), next-day scheduling hour, weekdays skipped for sale issue, cancel/return actions, ignored validation errors and statuses, repeated-order block threshold.\n- `invoicing` — preferred invoice series, auto-issue trigger and stock condition, payment methods excluded from auto-invoicing, only-if-paid flag, e-mail auto-send.\n- `proforma` — proforma series and the payment methods / status that trigger it.\n- `awb` — AWB auto-issue mode and stock condition, latest issue hour, skipped weekdays.\n- `status_sync` — the event → external status mapping used when BOCP pushes status changes back to your site.",
        "parameters": [
          {"in": "path", "name": "connector_id", "required": true, "schema": {"type": "integer"}, "description": "BOCP connector ID assigned when the connector was created."}
        ],
        "responses": {
          "200": {
            "description": "The connector configuration, grouped by area.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {"$ref": "#/components/schemas/ResponseEnvelope"},
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "object",
                          "properties": {
                            "connector":        {"type": "object", "description": "Identity: id, site_domain, type, active, order_api_url, order_event_notification_url."},
                            "products":         {"type": "object", "description": "Product auto-import options, update options, target sub-inventory, and push/feed settings."},
                            "order_processing": {"type": "object", "description": "Confirmation method, auto-process working point, on-import action, scheduling and automation guards."},
                            "invoicing":        {"type": "object", "description": "Invoice series and auto-issue rules."},
                            "proforma":         {"type": "object", "description": "Proforma series and auto-issue rules."},
                            "awb":              {"type": "object", "description": "AWB auto-issue rules and scheduling."},
                            "status_sync":      {"type": "object", "description": "Event to external-status mapping (events_statuses_mapping)."}
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "success": true,
                  "data": {
                    "connector": {
                      "id": 1,
                      "site_domain": "shop.example.ro",
                      "type": "BOCPRAPI",
                      "active": true,
                      "order_api_url": "https://secure.bocp.eu/app/rest/v1/2469/connector/1/order/{order_unique_id}/",
                      "order_event_notification_url": "https://shop.example.ro/bocp/notify"
                    },
                    "products": {
                      "autoimport_into_erp": true,
                      "import_mode": {"value": "import_only_ordered", "label": "ordered_only", "options": {"import_all": "all", "import_only_ordered": "ordered_only", "import_only_category_products": "only_categories_with_autoimport"}},
                      "update_options": {"update_product_code_in_erp": false, "disconnect_multiple_connections": true, "group_imported_products": true, "fields_updated_from_site": []},
                      "create_new_products_in_subinventory": {"id": 1, "name": "Marfa"},
                      "push": {"active": true, "products_feed_url": "https://secure.bocp.eu/app/rest/v1/2469/connector/1/products/", "product_change_notify_url": "https://shop.example.ro/bocp/notify"}
                    },
                    "order_processing": {
                      "order_confirm_method": {"value": "a", "label": "AUTOMAT: ...", "options": {"a": "AUTOMAT: ...", "m": "MANUAL: ..."}, "source": "global"},
                      "auto_process_workpoint": {"id": 3, "name": "Depozit central"},
                      "skip_bv_issue_on_iso_weekdays": [6, 7]
                    },
                    "invoicing": {"issue_invoice_only_if_paid": true, "autosend_invoice_via_email": false},
                    "status_sync": {"events_statuses_mapping": {"on_bv_issued": "in_processing", "on_awb_issued": "shipped"}}
                  }
                }
              }
            }
          },
          "401": {"$ref": "#/components/responses/Unauthorized"},
          "404": {"$ref": "#/components/responses/NotFound"}
        }
      }
    }
  }
}
