{
  "openapi": "3.0.3",
  "info": {
    "title": "MassTrade B2B Hub API",
    "description": "Operations marked deprecated + x-unused=true are unused by MassTrade ERP and must not be used from new clients. Routes still exist until a later delete pass.\n\nLaravel B2B hub HTTP API. Source file: resources/swagger/hub-api.json (this document). Catalog JSON and product pages stay on MassTrade ERP.\n\nAuth:\n- Public: shop list, login, password reset, PSP notify / redirect, order-by-hash.\n- Session cookie: guest cart and checkout (`sessions` middleware).\n- Bearer Passport: storefront after login, and ERP service user HUB_B2B on `/api/b2b/*`.\n- Ops token: GET /api/ops/logs via header X-Hub-Logs-Token (not Passport).\n\nHeader `X-MT-SID` is the machine SID. Blank defaults to APR. After login the storefront must send the shop SID. ERP clients set it on the Guzzle client.\n\n`X-MT-HUB-SECRET` is not used on these routes (ERP payment-config only).\n\nTry it out runs against this host. Do not fire PSP notify routes by hand.",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "/",
      "description": "This host"
    }
  ],
  "tags": [
    {
      "name": "Directory",
      "description": "Public shop picker. No Bearer. Sandbox rows omitted."
    },
    {
      "name": "Auth",
      "description": "Password grant, refresh, reset, logout. Deprecated register routes stay for BC."
    },
    {
      "name": "Cart",
      "description": "Session cart. Guest cookie or optional Bearer. Always pass account_id (seller). Laravel data.context plus additive hub_b2b_response. Twig / hubapi.js / Android."
    },
    {
      "name": "Cart v2",
      "description": "React-shaped cart on /api/v2/cart. Same session + account_id as Cart. Envelope Masstrade\\Contracts\\B2b\\Hub\\Cart\\CartV2Dto (sid, version 2, account_id, response, cart). No Laravel data wrap. No v2 twin of unused set_transport_location_point."
    },
    {
      "name": "Addresses",
      "description": "Buyer checkout addresses (session) and ERP CRM addresses (Bearer HUB_B2B)."
    },
    {
      "name": "Orders",
      "description": "Place and read orders. Guest checkout uses the session cart + PUT /api/orders."
    },
    {
      "name": "Payments",
      "description": "PSP redirects and webhooks. Public. Gateway-specific bodies."
    },
    {
      "name": "CRM",
      "description": "ERP writes contractors, addresses, and hub logins. Bearer HUB_B2B."
    },
    {
      "name": "Tenant",
      "description": "SID + account_id to ERP host, and seller shop upsert. Bearer HUB_B2B."
    },
    {
      "name": "Account",
      "description": "Current hub user. One login works in every shop."
    },
    {
      "name": "Claims v2",
      "description": "Buyer claim list. Live ERP /b2b/v2/claims. Bearer. Not GET /api/mt_returns (hub copy)."
    },
    {
      "name": "Customers",
      "description": "Desktop B2B / helper routes for buyers and addresses. Bearer."
    },
    {
      "name": "Seller",
      "description": "Supervisor switcher. Role seller, not the shop company."
    },
    {
      "name": "Stats",
      "description": "Seller-scope order statistics. Bearer."
    },
    {
      "name": "Ops",
      "description": "Remote diagnostics. Static header X-Hub-Logs-Token, not Passport. Table logs first; file tail when Postgres is down."
    },
    {
      "name": "Providers v2",
      "description": "Active, non-demo B2B shops for the storefront v2 directory. Public. Reuses ProviderListDto."
    }
  ],
  "paths": {
    "/api/providers": {
      "get": {
        "tags": [
          "Directory"
        ],
        "summary": "List non-sandbox B2B shops",
        "security": [],
        "responses": {
          "200": {
            "description": "Non-sandbox shops. ProviderListDto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderListDto"
                }
              }
            }
          }
        },
        "description": "Public shop directory for the apex picker (sellers.trade / b2b.masstrade.pl). No Bearer and no X-MT-SID scope. Omits sandbox shops (remote_sandbox=true). Returns { providers: [ { sid, account_id, active, name, domain_name, full_domain_name, storefront_domain_name, storefront_full_domain_name, logo, address } ] } (ProviderListDto). active is account_settings.local_active. full_domain_name is the legacy ERP/Twig shop host; storefront_full_domain_name is {slug}.sellers.trade for the React storefront."
      }
    },
    "/api/v2/providers": {
      "get": {
        "tags": [
          "Providers v2"
        ],
        "summary": "List active, non-demo B2B shops",
        "security": [],
        "responses": {
          "200": {
            "description": "Active, non-demo shops. ProviderListDto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProviderListDto"
                }
              }
            }
          }
        },
        "description": "Storefront v2 shop directory. Public (no Bearer, no X-MT-SID scope). Server-side filters to active shops only (account_settings.local_active=true) and drops demo / shared test shops by slug: `test` / `demo` and their `test.*` / `demo.*` subdomains, plus an explicit list of test-only slugs (`ProviderDirectory::EXCLUDED_SHOP_SLUGS`) that merely contain the word. Returns the same ProviderListDto as /api/providers."
      }
    },
    "/api/proxy/login": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Password grant via Passport",
        "security": [],
        "responses": {
          "200": {
            "description": "Passport token + expires_at",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportTokenWithExpiresAt"
                }
              }
            }
          },
          "401": {
            "description": "Passport rejected the grant, or supervisor_approval is false/null. In the supervisor case the JSON body is not rewritten (Passport payload stays); the HTTP reason phrase is the translated message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportOAuthError"
                }
              }
            }
          },
          "422": {
            "description": "Laravel validation failed (username, password, client_id required).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "OAuth2 password grant via Passport. Used by React, Android, HubApi.js, and ERP B2BHub.php. No Bearer. Starts a Laravel session cookie. On success returns the Passport token plus expires_at and clears the session cart. Pending supervisor_approval returns 401.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "username",
                  "password",
                  "client_id"
                ],
                "properties": {
                  "username": {
                    "type": "string"
                  },
                  "password": {
                    "type": "string",
                    "format": "password"
                  },
                  "client_id": {
                    "type": "string",
                    "description": "Passport client id (usually 2)"
                  },
                  "provider_account_id": {
                    "type": "integer",
                    "description": "Seller ERP account id"
                  },
                  "locale": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/proxy/refresh": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Refresh session / token",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Passport token + expires_at (same shape as login).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportTokenWithExpiresAt"
                }
              }
            }
          },
          "401": {
            "description": "Passport rejected the refresh grant.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportOAuthError"
                }
              }
            }
          },
          "422": {
            "description": "Laravel validation failed (client_id and refresh_token required).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "Refresh a Passport token. Client sends client_id and refresh_token (query or body). Same response shape as login.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "client_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "refresh_token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/api/proxy/forgot_password": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Start password reset mail",
        "security": [],
        "responses": {
          "200": {
            "description": "Reset link mailed. message is translated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "404": {
            "description": "Email unknown. message is translated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "422": {
            "description": "Laravel validation failed (email required).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "Start password reset. Body: email (required), optional lang for the mail locale and back_url (SPA origin, CORS-allowlist-validated). 200 on success, 404 if the email is unknown. The mail links to {back_url}/password/reset/{token}; without a trusted back_url it falls back to the hub Blade page.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "lang": {
                    "type": "string"
                  },
                  "back_url": {
                    "type": "string",
                    "description": "SPA origin for the reset link. Validated against the CORS allowlist; falls back to the hub when absent or untrusted."
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/proxy/password_reset": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Reset password",
        "security": [],
        "responses": {
          "200": {
            "description": "Password reset. message is translated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid or expired token, or validation failed. message is translated.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageResponse"
                }
              }
            }
          }
        },
        "description": "Reset a password with the token mailed by POST /api/proxy/forgot_password. Body: token, email, password, password_confirmation. JSON only — the SPA screen; Blade POST /password/reset stays for BC. Does not log the buyer in.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "token",
                  "email",
                  "password",
                  "password_confirmation"
                ],
                "properties": {
                  "token": {
                    "type": "string"
                  },
                  "email": {
                    "type": "string",
                    "format": "email"
                  },
                  "password": {
                    "type": "string",
                    "minLength": 6
                  },
                  "password_confirmation": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Passport token endpoint",
        "security": [],
        "responses": {
          "200": {
            "description": "Passport token JSON (no expires_at).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportToken"
                }
              }
            }
          },
          "400": {
            "description": "OAuth error (invalid request / grant / client).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportOAuthError"
                }
              }
            }
          },
          "401": {
            "description": "Invalid credentials or client.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PassportOAuthError"
                }
              }
            }
          }
        },
        "description": "Laravel Passport token endpoint (password / refresh). Prefer POST /api/proxy/login from storefronts; this is the raw grant.",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/PassportTokenRequest"
              }
            }
          }
        }
      }
    },
    "/api/logout": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Revoke current API token",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Empty JSON array. Controller returns json([]) and does not call revoke() on the token.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "maxItems": 0,
                  "items": {}
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid Bearer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Unauthenticated"
                }
              }
            }
          }
        },
        "description": "Revoke the current Passport access token. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/register": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Deprecated registration",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Deprecated hub registration. Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/sync_b2b": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Sync (upsert) B2B customer, user and address",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Idempotent B2B customer+user+address sync (upsert), not a one-shot registration.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/register_channel_user": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Deprecated channel user registration",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Deprecated channel-user registration. Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/register_demo_user": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Deprecated demo user registration",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Deprecated demo-user registration. Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/cart": {
      "get": {
        "tags": [
          "Cart"
        ],
        "summary": "Read session cart",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Read or create the session cart for a seller. Guest (cookie) or signed-in buyer (optional Bearer). Required: account_id (seller ERP id). Optional: sandbox, apiname (default mobile). Response is CartContextResource (products, summary, selected transport/payment/address, allowed_*, messages, user) under Laravel data, plus sibling hub_b2b_response (CART_READ when there are no messages).",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sandbox",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "apiname",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "mobile"
            }
          }
        ]
      },
      "put": {
        "tags": [
          "Cart"
        ],
        "summary": "Replace session cart lines",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          },
          "403": {
            "description": "Supervisor without a selected contractor (abort 403). Laravel abort text — not the cart envelope. Cart business 403/406/409 (blocked, stock, multi-seller) stay HTTP 200 with hub_b2b_response."
          }
        },
        "description": "Add, change, or remove a cart line. Guest or signed-in. Required: account_id, cart_pid, cart_quantity. Optional quantity_mode (incremental|set), force_add, apiname, sandbox. Checks contractor block, multi-seller, and ERP product availability (MTApi::getProduct). Those business outcomes stay HTTP 200 (messages[].code 403/406/409 + hub_b2b_response CART_BLOCKED / CART_UNAVAILABLE / CART_CONFLICT). Real HTTP 403 is only abort when a supervisor has no contractor selected.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "cart_pid",
                  "cart_quantity"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "cart_pid": {
                    "type": "integer"
                  },
                  "cart_quantity": {
                    "type": "number"
                  },
                  "quantity_mode": {
                    "type": "string",
                    "enum": [
                      "incremental",
                      "set"
                    ]
                  },
                  "force_add": {
                    "type": "boolean"
                  },
                  "apiname": {
                    "type": "string"
                  },
                  "sandbox": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      },
      "delete": {
        "tags": [
          "Cart"
        ],
        "summary": "Clear session cart",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Clear the session cart for account_id. Returns an empty cart context plus hub_b2b_response CART_CLEARED.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/api/cart/set_transport_key": {
      "put": {
        "tags": [
          "Cart"
        ],
        "summary": "Set cart transport",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Set cart transport_key and refresh the transport line. Required: transport_key, account_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "transport_key"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "transport_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/cart/set_transport_location_point": {
      "put": {
        "tags": [
          "Cart"
        ],
        "summary": "Unused — set pickup / locker point",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Route exists in routes/api.php. CartController has no method for it — do not call from new clients until it is implemented.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/cart/set_payment_type": {
      "put": {
        "tags": [
          "Cart"
        ],
        "summary": "Set cart payment type",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Set cart payment_type. Required: payment_type, account_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "payment_type"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "payment_type": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/cart/set_comment": {
      "put": {
        "tags": [
          "Cart"
        ],
        "summary": "Set cart comment",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Set the order comment on the cart. Required: comment, account_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "comment"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "comment": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/cart/set_address_id": {
      "put": {
        "tags": [
          "Cart"
        ],
        "summary": "Set cart address",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Attach a delivery address to the cart. Required: address_id, account_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "address_id"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "address_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/cart/load_by_order": {
      "post": {
        "tags": [
          "Cart"
        ],
        "summary": "Unused — load cart from an order",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartOk"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Load cart lines from an existing order (LoadCartByOrderRequest). Required: order_id, account_id. Do not use from new clients.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "order_id"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "order_id": {
                    "type": "integer"
                  },
                  "sandbox": {
                    "type": "boolean"
                  },
                  "apiname": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/v2/cart": {
      "get": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Read session cart (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Same session cart as GET /api/cart. JSON is CartV2Dto: {sid, version: 2, account_id, response, cart}. response is HubB2bResponse (nested, not sibling hub_b2b_response). Required: account_id. Optional: sandbox, apiname (default mobile).",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "sandbox",
            "in": "query",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "apiname",
            "in": "query",
            "schema": {
              "type": "string",
              "default": "mobile"
            }
          }
        ]
      },
      "put": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Replace session cart lines (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          },
          "403": {
            "description": "Supervisor without a selected contractor (abort 403). Not the cart envelope. Cart business 403/406/409 stay HTTP 200 with nested response CART_BLOCKED / CART_UNAVAILABLE / CART_CONFLICT."
          }
        },
        "description": "Same body as PUT /api/cart (account_id, cart_pid, cart_quantity; optional quantity_mode, force_add, apiname, sandbox). Response is CartV2Dto. Clients switch on nested response.reason, not message text. When code is CART_UNAVAILABLE and the reject is stock: out_of_stock if cart.products has no line for cart_pid, no_more_stock if that pid already has quantity > 0.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "cart_pid",
                  "cart_quantity"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "cart_pid": {
                    "type": "integer"
                  },
                  "cart_quantity": {
                    "type": "number"
                  },
                  "quantity_mode": {
                    "type": "string",
                    "enum": [
                      "incremental",
                      "set"
                    ]
                  },
                  "force_add": {
                    "type": "boolean"
                  },
                  "apiname": {
                    "type": "string"
                  },
                  "sandbox": {
                    "type": "boolean"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      },
      "delete": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Clear session cart (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Clear the session cart for account_id. Returns CartV2Dto with response CART_CLEARED.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/api/v2/cart/set_transport_key": {
      "put": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Set cart transport (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Same body as PUT /api/cart/set_transport_key. Response is CartV2Dto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "transport_key"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "transport_key": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/v2/cart/set_payment_type": {
      "put": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Set cart payment type (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Same body as PUT /api/cart/set_payment_type. Response is CartV2Dto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "payment_type"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "payment_type": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/v2/cart/set_comment": {
      "put": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Set cart comment (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Same body as PUT /api/cart/set_comment. Response is CartV2Dto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "comment"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "comment": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/v2/cart/set_address_id": {
      "put": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Set cart address (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Same body as PUT /api/cart/set_address_id. Response is CartV2Dto.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "address_id"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "address_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/v2/cart/load_by_order": {
      "post": {
        "tags": [
          "Cart v2"
        ],
        "summary": "Load cart from an order (v2)",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/CartV2Ok"
          }
        },
        "description": "Same body as POST /api/cart/load_by_order. Response is CartV2Dto. Not unused on v2 (React may call it).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id",
                  "order_id"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "order_id": {
                    "type": "integer"
                  },
                  "sandbox": {
                    "type": "boolean"
                  },
                  "apiname": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/customer/addresses/{address_id}": {
      "get": {
        "tags": [
          "Addresses"
        ],
        "summary": "List or get customer addresses",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "One AddressResource when address_id is set. Laravel data wrap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResourceData"
                }
              }
            }
          },
          "404": {
            "description": "Not found. CustomerAddressController returns response()->json(null, 404).",
            "content": {
              "application/json": {
                "schema": {
                  "nullable": true
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "List buyer addresses, or one address when address_id is set. Session guest or signed-in user. AddressResource / collection."
      },
      "put": {
        "tags": [
          "Addresses"
        ],
        "summary": "Update customer address",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Remaining address collection after update. Laravel data wrap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResourceCollection"
                }
              }
            }
          },
          "404": {
            "description": "Not found. CustomerAddressController returns response()->json(null, 404).",
            "content": {
              "application/json": {
                "schema": {
                  "nullable": true
                }
              }
            }
          },
          "422": {
            "description": "Laravel ValidationException.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Update a checkout address. Same body as POST /api/customer/addresses.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddressWrite"
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Addresses"
        ],
        "summary": "Delete customer address",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Remaining address collection after delete. Laravel data wrap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResourceCollection"
                }
              }
            }
          },
          "404": {
            "description": "Not found. CustomerAddressController returns response()->json(null, 404).",
            "content": {
              "application/json": {
                "schema": {
                  "nullable": true
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Delete a checkout address. Returns the remaining collection."
      }
    },
    "/api/customer/addresses": {
      "post": {
        "tags": [
          "Addresses"
        ],
        "summary": "Create customer address",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "User address collection after create. Laravel data wrap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResourceCollection"
                }
              }
            }
          },
          "422": {
            "description": "Laravel ValidationException.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "Create a checkout address. Guest writes SessionAddress; signed-in writes Address. Required: name, surname, postcode, city, street, number_1, country_id, province_id, is_company. Optional: number_2, company_name, company_vat, phones, emails, VAT flags, apiname. Returns the user's address collection.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddressWrite"
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Addresses"
        ],
        "summary": "List customer addresses",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "description": "List buyer addresses. Route is GET /api/customer/addresses/{address_id?} with address_id omitted. AddressResource::collection. Session guest or signed-in user.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "responses": {
          "200": {
            "description": "AddressResource collection. Laravel data wrap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddressResourceCollection"
                }
              }
            }
          }
        }
      }
    },
    "/api/guest_orders": {
      "put": {
        "tags": [
          "Orders"
        ],
        "summary": "Unused — place guest order",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Route points at OrderController@createGuestOrder but the method is fully commented out. Do not call. Use PUT /api/orders with a session cart.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/guest_orders/context": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "Guest checkout context",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "CartContextResource wrap (data.context). Does not attach hub_b2b_response (no shared cartContext() emit path).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CartContextData"
                }
              }
            }
          }
        },
        "description": "Guest checkout preview without a persisted cart. Body: order (map pid → quantity, required) and account_id. Optional sandbox, apiname, transport_key. Returns CartContextResource.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/orders": {
      "put": {
        "tags": [
          "Orders"
        ],
        "summary": "Place signed-in order",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "OrderResource. Laravel data wrap. No hub_b2b_response this pass.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResourceData"
                }
              }
            }
          },
          "422": {
            "description": "Laravel ValidationException.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "Place an order from the session cart (React checkout). Required: account_id. Optional: apiname, sandbox, comment, transport_key, payment_type, address_id, from_platform / from_os / from_client_version, ip, password (guest: also create a hub user). Validates min order value, clears the cart, sends mail. Response: OrderResource (id, hash, lines, totals, address, status, pay_url).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "account_id"
                ],
                "properties": {
                  "account_id": {
                    "type": "integer"
                  },
                  "apiname": {
                    "type": "string"
                  },
                  "sandbox": {
                    "type": "boolean"
                  },
                  "comment": {
                    "type": "string"
                  },
                  "transport_key": {
                    "type": "string"
                  },
                  "payment_type": {
                    "type": "string"
                  },
                  "address_id": {
                    "type": "integer"
                  },
                  "password": {
                    "type": "string",
                    "description": "Guest: create hub user on place"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/orders/{order_id}/hash/{hash}": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "Get order by public hash",
        "security": [],
        "responses": {
          "200": {
            "description": "Public OrderResource by id + hash. Laravel data wrap.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderResourceData"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Public order read by id + hash (confirmation / payment return). No Bearer. Loads Order::withoutSidScope(). OrderResource."
      }
    },
    "/api/order_list": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "List orders for the current user",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "List orders for the current hub user. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/count": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "List buyer orders with a total count",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OrderResource rows plus orders_count. Nested Laravel paginator wrap (orders.data) is still accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrderCountResponse"
                }
              }
            }
          }
        },
        "description": "Buyer order history for the storefront (S6). Same filters as GET /api/orders without an id, plus orders_count. Required for a shop list: account_id. Optional: per_page (1–200, default 20; the React shop sends 10), offset (0-based), page (1-based, sent together with offset), query (order id, address, comment, tracking, line title, SKU, pid), order_field, order_type. Bearer required. Not GET /api/order_list (seller, last 14 days).",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            }
          },
          {
            "name": "query",
            "in": "query",
            "description": "Matches order id, address, comment, tracking number, and line title / SKU / pid.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order_field",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["asc", "desc"]
            }
          }
        ]
      }
    },
    "/api/orders/{order_id}": {
      "get": {
        "tags": [
          "Orders"
        ],
        "summary": "Get order",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "order_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Get one order for the current user. Bearer required. Omit order_id for the list variant on this route."
      }
    },
    "/api/orders/set_tracking/{order}": {
      "post": {
        "tags": [
          "Orders"
        ],
        "summary": "Set tracking on an order",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "order",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Set tracking data on an order (seller / ERP). Bearer required."
      }
    },
    "/api/payments/notify/payu": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "PayU notification",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "PayU payment notification webhook. Public. Body is PayU's format, not a hub DTO.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/payments/redirect/payu/{payment_order}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "PayU return redirect",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "payment_order",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Buyer return / redirect into the PayU gateway. payment_order is the hub order id."
      }
    },
    "/api/payments/redirect/bluemedia/{payment_order}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Autopay return redirect",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "payment_order",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Buyer redirect into Autopay / BlueMedia. payment_order is the hub order id."
      }
    },
    "/api/payments/notify/bluemedia": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Autopay notification (GET)",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Autopay / BlueMedia notification (GET).",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/payments/notify/bluemedia/itn": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Autopay ITN",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Autopay ITN — main payment-status handler. Public. Gateway-specific body.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/payments/notify/bluemedia/rpan": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Autopay RPAN",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Autopay RPAN stub (logs only). Do not rely on this.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/payments/notify/bluemedia/rpdn": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Autopay RPDN",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Autopay RPDN stub (logs only). Do not rely on this.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/payments/redirect/przelewy24/{payment_order}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "Przelewy24 return redirect",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "payment_order",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Buyer redirect into Przelewy24. payment_order is the hub order id."
      }
    },
    "/api/payments/notify/przelewy24": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "Przelewy24 notification",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Przelewy24 notification webhook. Public. Gateway-specific body.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/payments/redirect/paypal/{payment_order}": {
      "get": {
        "tags": [
          "Payments"
        ],
        "summary": "PayPal return redirect",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "payment_order",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Buyer redirect into PayPal. payment_order is the hub order id."
      }
    },
    "/api/payments/notify/paypal": {
      "post": {
        "tags": [
          "Payments"
        ],
        "summary": "PayPal notification",
        "security": [],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "PayPal notification webhook. Public. Gateway-specific body.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/customers": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "List CRM customers",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "List B2B contractors in the ERP account scope of the Bearer user (HUB_B2B). Query: search, order_by, order_direction. Only rows with mt_customer_id set. B2BCustomerResource collection.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/customer/verify": {
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Unused — verify CRM customer payload",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Pre-flight validation before creating a contractor (B2BCustomerCreateRequest). Not in the ERP hubB2BRequest allow-list. Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/b2b/customer": {
      "put": {
        "tags": [
          "CRM"
        ],
        "summary": "Create CRM customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Create a B2B contractor. Bearer HUB_B2B. Required: mt_server_id, mt_hostname, mt_account_id. Optional: remote_balance, remote_blocked. B2BCustomerResource.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/customer/{b2bCustomer}": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "Get CRM customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "b2bCustomer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Get one contractor (bound to allowed ERP accounts). Bearer HUB_B2B."
      },
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Update CRM customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "b2bCustomer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Update contractor remote_balance / remote_blocked. Bearer HUB_B2B."
      }
    },
    "/api/b2b/customer/blocked/{b2bCustomer}/{set_blocked}": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "Set remote blocked flag",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "b2bCustomer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "set_blocked",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Set remote_blocked (0/1 in the path). Bearer HUB_B2B. Empty 200."
      }
    },
    "/api/b2b/address/verify": {
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Unused — verify CRM address payload",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Pre-flight validation of a CRM address payload. Not in the ERP hubB2BRequest allow-list. Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/b2b/address/{b2bCustomer}": {
      "put": {
        "tags": [
          "CRM"
        ],
        "summary": "Create CRM address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "b2bCustomer",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Create a contractor address. Bearer HUB_B2B. Required: name, postcode, city, street, country_id, province_id, is_company, mt_address_id. AddressResource."
      }
    },
    "/api/b2b/address/{address}": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "Get CRM address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Get a CRM address by hub id (account-scoped binding). Bearer HUB_B2B."
      }
    },
    "/api/b2b/address/customer/{b2b_customer_id}": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "Default address for customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "b2b_customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Default (or first) address for a contractor. AddressResource or null. Bearer HUB_B2B."
      }
    },
    "/api/b2b/address/{address}/users": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "Users on a CRM address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Hub users attached to this address, plus global payers. B2BLoginAccountResource collection."
      }
    },
    "/api/b2b/address/{address_id}": {
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Update CRM address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Update a CRM address. Same fields as create plus user_id (contractor id). Bearer HUB_B2B."
      }
    },
    "/api/b2b/address/connectUser/{address}/{user}": {
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Attach user to address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Attach a hub user to an address and contractor. Bearer HUB_B2B."
      }
    },
    "/api/b2b/address/disconnectUser/{address}/{user}": {
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Detach user from address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Detach a hub user from an address. Bearer HUB_B2B."
      }
    },
    "/api/b2b/addresses": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "List CRM addresses",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Paginated CRM addresses. Query: per_page, search, is_default, customer_id, mt_symkar, order_by, order_direction. Bearer HUB_B2B.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/user/verify": {
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Verify CRM login account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Pre-flight validation before creating a hub login (B2BLoginAccountVerifyRequest). Bearer HUB_B2B.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/user": {
      "put": {
        "tags": [
          "CRM"
        ],
        "summary": "Create CRM login account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Create a B2B login. Bearer HUB_B2B. Required: address_id, email, password_repeat; password min 6. Optional: role, locale, payer, newsletter, email_autoconfirm, connected_seller_email, contact fields. Links user ↔ address ↔ contractor. B2BLoginAccountResource.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/user/{user}": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "Get CRM login account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Get one hub login. Bearer HUB_B2B."
      },
      "post": {
        "tags": [
          "CRM"
        ],
        "summary": "Update CRM login account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "user",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Update a hub login (password, payer, newsletter, contact, locale). Bearer HUB_B2B."
      }
    },
    "/api/b2b/users": {
      "get": {
        "tags": [
          "CRM"
        ],
        "summary": "List CRM login accounts",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "List hub logins in ERP account scope. Query: role, search. Bearer HUB_B2B.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/sid": {
      "put": {
        "tags": [
          "Tenant"
        ],
        "summary": "Activate SID + account_id host",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Activate or create mt_sid_hosts for a sid + account_id pair. Does not write account_settings. Bearer HUB_B2B (ERP channel save). Required: sid, account_id, host. Optional: protocol (http|https), label. Response: MtSidHostResource including erp_base_url.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sid",
                  "account_id",
                  "host"
                ],
                "properties": {
                  "sid": {
                    "type": "string"
                  },
                  "account_id": {
                    "type": "integer",
                    "minimum": 1
                  },
                  "host": {
                    "type": "string"
                  },
                  "protocol": {
                    "type": "string",
                    "enum": [
                      "http",
                      "https"
                    ]
                  },
                  "label": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      },
      "post": {
        "tags": [
          "Tenant"
        ],
        "summary": "Unused — update SID + account_id host",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Update host / protocol / label for an existing sid + account_id. ERP HubSellerSync uses PUT (activate) and POST /api/b2b/sid/deactivate only. Do not use from new clients.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sid",
                  "account_id"
                ],
                "properties": {
                  "sid": {
                    "type": "string"
                  },
                  "account_id": {
                    "type": "integer"
                  },
                  "host": {
                    "type": "string"
                  },
                  "protocol": {
                    "type": "string",
                    "enum": [
                      "http",
                      "https"
                    ]
                  },
                  "label": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/b2b/sid/deactivate": {
      "post": {
        "tags": [
          "Tenant"
        ],
        "summary": "Deactivate SID + account_id",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Set active=false for the pair. Row stays. Bearer HUB_B2B. Body: sid, account_id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sid",
                  "account_id"
                ],
                "properties": {
                  "sid": {
                    "type": "string"
                  },
                  "account_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/b2b/sid/{sid}/{account_id}": {
      "get": {
        "tags": [
          "Tenant"
        ],
        "summary": "Unused — read SID + account_id row",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "sid",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "account_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Unused. MassTrade does not call this. Deprecated. Read one SID host row, including inactive. No ERP call site. Do not use from new clients.",
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/b2b/account_settings": {
      "put": {
        "tags": [
          "Tenant"
        ],
        "summary": "Upsert seller shop settings",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Upsert the seller shop row. Key (sid, remote_account_id, remote_sandbox). ERP HubSellerSync calls this on mobile save / toggle / delete. Body: sid, remote_account_id (alias account_id); optional remote_sandbox, local_active, domain_name, full_domain_name, theme, config, config_remote, seller, channels, payment_def, payments_enabled, portal_main_category_id, default_transport_transport_pricing_id. AccountSettingsResource.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "sid"
                ],
                "properties": {
                  "sid": {
                    "type": "string"
                  },
                  "remote_account_id": {
                    "type": "integer"
                  },
                  "account_id": {
                    "type": "integer",
                    "description": "Alias of remote_account_id"
                  },
                  "remote_sandbox": {
                    "type": "boolean"
                  },
                  "sandbox": {
                    "type": "boolean",
                    "description": "Alias of remote_sandbox"
                  },
                  "local_active": {
                    "type": "boolean"
                  },
                  "domain_name": {
                    "type": "string"
                  },
                  "full_domain_name": {
                    "type": "string"
                  },
                  "theme": {
                    "type": "string"
                  },
                  "config": {
                    "type": "object"
                  },
                  "config_remote": {
                    "type": "object"
                  },
                  "seller": {
                    "type": "object"
                  },
                  "channels": {
                    "type": "object"
                  },
                  "payment_def": {
                    "type": "object"
                  },
                  "payments_enabled": {
                    "type": "object"
                  }
                }
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/user": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Current hub user",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK — hub user row plus shop identity (`mt_customer_id`, `remote_account_id`, `sandbox`) and ERP buyer credit (`idc_limit*`) when the contractor + shop account resolve.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HubUser"
                }
              }
            }
          }
        },
        "description": "Current hub user (Passport). One email works across shops; shop is X-MT-SID + account_settings. `mt_customer_id` is the ERP contractor PK for ONE shop (`account_id` query, else the only shop the user is a contractor at; -1 when ambiguous) — clients pass it to identify the buyer, e.g. as `return_created_by__customerid` on the returns collector. `sandbox` mirrors `users.mt_sandbox`. After Bearer, Hub may call ERP `GET /b2b/v1/buyer-credit` (X-MT-HUB-SECRET) and merge `idc_limit`, `idc_limit_available`, `idc_limit_exceeded` (zeros when missing / guest / ERP error).",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "ERP account of the shop whose contractor to resolve. Required when the buyer is a contractor at more than one shop.",
            "schema": {
              "type": "integer"
            }
          }
        ]
      }
    },
    "/api/user/change_password": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Change password",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Change password for the current hub user. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/user/store_device_info": {
      "put": {
        "tags": [
          "Account"
        ],
        "summary": "Unused — store device info",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Store device info for the current user (mobile / PWA leftover). Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/user/register_b2b_supervisor_approved": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Supervisor-approved B2B register",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Mark a B2B registration as supervisor-approved. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/user/change_currency": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Change currency",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK. Persists users.user_currency and clears the cart."
          }
        },
        "description": "Persist the buyer currency. Clears the cart. Bearer required. Does not validate the shop allow-list from ERP GET /b2b/v2/currencies.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "currency",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "PLN",
                "USD",
                "EUR",
                "GBP",
                "RUB"
              ]
            }
          }
        ]
      }
    },
    "/api/user/change_locale": {
      "post": {
        "tags": [
          "Account"
        ],
        "summary": "Change locale",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Change the current user's locale. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/user/allowedAddresses/{providerAccountID}/{userID}": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Addresses allowed for a user",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "providerAccountID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "userID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Addresses the given user may use at this seller account. Bearer required."
      }
    },
    "/api/mt_returns": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Buyer returns list",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Buyer returns list from the hub (ERP remains the seller returns source). Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/v2/claims": {
      "get": {
        "tags": [
          "Claims v2"
        ],
        "summary": "Buyer claims list (v2)",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "description": "Live list from ERP GET /b2b/v2/claims. Hub fills customer_id from b2_b_customers.mt_customer_id. SPA never sends customer_id. Same JSON as ERP (ClaimsV2Dto, no Laravel data wrap). status=open|overdue|closed|all (default all). Every row carries overdue (bool) — ERP decides it from the account threshold (modul_zwrotow.dni_przeterminowania), so the SPA must render it, never recompute it. per_page default 10, max 50. ERP down → 502. Not GET /api/mt_returns.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "account_id",
            "in": "query",
            "required": false,
            "description": "Required when the buyer is a contractor at more than one shop.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "overdue",
                "closed",
                "all"
              ],
              "default": "all"
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 10,
              "maximum": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "sandbox",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "ClaimsV2Dto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ClaimsV2"
                }
              }
            }
          },
          "400": {
            "description": "Missing account_id when the buyer has several shops."
          },
          "401": {
            "description": "No Bearer token."
          },
          "403": {
            "description": "No contractor at this shop."
          },
          "502": {
            "description": "ERP unavailable or invalid claims envelope."
          }
        }
      }
    },
    "/api/customers/info": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Customers info",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Buyer / contractor info helper for the desktop B2B UI. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/customers/by_address_account_id": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Unused — customers by address account",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Unused. MassTrade does not call this. Deprecated. Customers filtered by address account id. No ERP call site. Do not use from new clients.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ],
        "deprecated": true,
        "x-unused": true
      }
    },
    "/api/customers/{user_id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Get customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Get a customer / hub user by id. Bearer required."
      },
      "delete": {
        "tags": [
          "Customers"
        ],
        "summary": "Delete customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Delete a customer / hub user. Bearer required."
      }
    },
    "/api/customers/address/{address_id}": {
      "put": {
        "tags": [
          "Customers"
        ],
        "summary": "Save customer address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Save a customer address from the desktop B2B UI. Bearer required."
      }
    },
    "/api/customers/password/{customer_id}": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Set customer password",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Set a customer password. Bearer required."
      }
    },
    "/api/customers/address_list/{mt_customer_id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Addresses for a customer",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "mt_customer_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Addresses for an ERP customer id. Bearer required."
      }
    },
    "/api/customers/users_by_address/{address_id}": {
      "get": {
        "tags": [
          "Customers"
        ],
        "summary": "Users on an address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          },
          {
            "name": "address_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "description": "Hub users attached to an address. Bearer required."
      }
    },
    "/api/customers/set_addresses_to_users": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Assign addresses to users",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Assign addresses to users. Bearer required. Desktop B2B / ERP helper.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/customers/set_users_to_address": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Assign users to an address",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Assign users to an address. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/customers/set_seller_to_users": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Assign seller to users",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Assign a seller (supervisor) to users. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/customers/set_remote_blocked": {
      "post": {
        "tags": [
          "Customers"
        ],
        "summary": "Set remote blocked",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Set remote_blocked on customers. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/seller/accounts": {
      "get": {
        "tags": [
          "Seller"
        ],
        "summary": "Seller account list",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Seller / supervisor account list (switcher). Bearer, role seller.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/seller/switch_user": {
      "post": {
        "tags": [
          "Seller"
        ],
        "summary": "Seller switch user",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Supervisor switches into a buyer user. Bearer, role seller.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/stats/daily_orders_count": {
      "get": {
        "tags": [
          "Stats"
        ],
        "summary": "Daily order count",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Daily order count for the current seller scope. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/stats/daily_orders_value": {
      "get": {
        "tags": [
          "Stats"
        ],
        "summary": "Daily order value",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Daily order value for the current seller scope. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/stats/orders_per_user": {
      "get": {
        "tags": [
          "Stats"
        ],
        "summary": "Orders per user",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Orders per user. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/stats/clients_and_supervisors_stats_data": {
      "get": {
        "tags": [
          "Stats"
        ],
        "summary": "Clients and supervisors stats",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "OK"
          }
        },
        "description": "Clients and supervisors dashboard numbers. Bearer required.",
        "parameters": [
          {
            "$ref": "#/components/parameters/XMtSid"
          }
        ]
      }
    },
    "/api/ops/logs": {
      "get": {
        "tags": [
          "Ops"
        ],
        "summary": "Read hub logs (table, files on DB failure)",
        "security": [
          {
            "opsLogsToken": []
          }
        ],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "auto (default) = table then files. table = 503 if DB is down. files = skip the table.",
            "schema": {
              "type": "string",
              "enum": [
                "auto",
                "table",
                "files"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Table page size (1-500, default 100).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Filter logs.type (info, warning, error, debug).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tag",
            "in": "query",
            "required": false,
            "description": "Filter logs.tag (cron, cart_update_start, …).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before_id",
            "in": "query",
            "required": false,
            "description": "Older page: id < before_id.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Substring match on logs.data.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "file",
            "in": "query",
            "required": false,
            "description": "When reading files: basename only (laravel-2026-09-09.log).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bytes",
            "in": "query",
            "required": false,
            "description": "File tail size (default 65536, max 262144).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Table events, or file tail when source=files or the table read failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpsLogsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong X-Hub-Logs-Token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          },
          "503": {
            "description": "source=table and Postgres is down.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationError"
                }
              }
            }
          }
        },
        "description": "Ops dump. Header X-Hub-Logs-Token (query token also accepted). Default reads the logs table (LogManager). When that query throws, the same response falls back to a tail of storage/logs/*.log and sets fallback=true. Does not use auth:api or sessions, so it still works during a DB outage."
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "Passport",
        "description": "Laravel Passport access token. Storefront buyers after POST /api/proxy/login. ERP uses the HUB_B2B service user on /api/b2b/*."
      },
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "laravel_session",
        "description": "Laravel session cookie from the sessions middleware. Guest cart and checkout."
      },
      "opsLogsToken": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Hub-Logs-Token",
        "description": "Static ops token for GET /api/ops/logs. Interim hardcoded in App\\Support\\OpsLogsToken. Not a Passport bearer."
      }
    },
    "parameters": {
      "XMtSid": {
        "name": "X-MT-SID",
        "in": "header",
        "required": false,
        "description": "Masked machine SID (e.g. EEE). Blank or missing defaults to APR.",
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "ProviderAddressDto": {
        "type": "object",
        "description": "Public company address on GET /api/providers. No bank account. Masstrade\\Contracts\\B2b\\Shop\\ProviderAddressDto.",
        "properties": {
          "company_name": {
            "type": "string"
          },
          "vat": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "number_1": {
            "type": "string"
          },
          "number_2": {
            "type": "string"
          },
          "postcode": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "country": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          },
          "email": {
            "type": "string"
          }
        }
      },
      "ProviderDto": {
        "type": "object",
        "description": "One public B2B shop. Identity is (sid, account_id). active is account_settings.local_active. Masstrade\\Contracts\\B2b\\Shop\\ProviderDto.",
        "required": [
          "sid",
          "account_id",
          "active",
          "name",
          "domain_name",
          "full_domain_name",
          "storefront_domain_name",
          "storefront_full_domain_name",
          "address"
        ],
        "properties": {
          "sid": {
            "type": "string"
          },
          "account_id": {
            "type": "integer",
            "minimum": 1
          },
          "active": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "domain_name": {
            "type": "string"
          },
          "full_domain_name": {
            "type": "string",
            "description": "Legacy ERP/Twig shop host, e.g. zolux.masstrade.pl."
          },
          "storefront_domain_name": {
            "type": "string",
            "description": "Shop slug for the React storefront (same as domain_name when it is a simple label)."
          },
          "storefront_full_domain_name": {
            "type": "string",
            "description": "React shop Host, e.g. zolux.sellers.trade."
          },
          "logo": {
            "type": "string",
            "nullable": true,
            "description": "Absolute URL, or null when no theme/seller icon."
          },
          "address": {
            "$ref": "#/components/schemas/ProviderAddressDto"
          }
        }
      },
      "ProviderListDto": {
        "type": "object",
        "description": "Envelope for GET /api/providers. Masstrade\\Contracts\\B2b\\Shop\\ProviderListDto.",
        "required": [
          "providers"
        ],
        "properties": {
          "providers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProviderDto"
            }
          }
        }
      },
      "PassportToken": {
        "type": "object",
        "description": "Laravel Passport AccessTokenController JSON (password / refresh grant).",
        "properties": {
          "token_type": {
            "type": "string",
            "example": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "description": "Seconds until expiry."
          },
          "access_token": {
            "type": "string"
          },
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "PassportTokenWithExpiresAt": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PassportToken"
          },
          {
            "type": "object",
            "properties": {
              "expires_at": {
                "type": "integer",
                "description": "Unix seconds. ProxyController adds time() + expires_in when expires_in is present."
              }
            }
          }
        ]
      },
      "PassportTokenRequest": {
        "type": "object",
        "description": "Passport /oauth/token form body. password grant needs username + password; refresh_token grant needs refresh_token.",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "password",
              "refresh_token"
            ]
          },
          "client_id": {
            "type": "string"
          },
          "client_secret": {
            "type": "string",
            "format": "password"
          },
          "username": {
            "type": "string"
          },
          "password": {
            "type": "string",
            "format": "password"
          },
          "refresh_token": {
            "type": "string"
          },
          "scope": {
            "type": "string"
          }
        }
      },
      "PassportOAuthError": {
        "type": "object",
        "description": "league/oauth2-server error JSON returned by Passport.",
        "properties": {
          "error": {
            "type": "string"
          },
          "error_description": {
            "type": "string"
          },
          "hint": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ValidationError": {
        "type": "object",
        "description": "Laravel ValidationException JSON (422).",
        "properties": {
          "message": {
            "type": "string"
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "MessageResponse": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "Unauthenticated": {
        "type": "object",
        "description": "Laravel auth:api JSON when Bearer is missing or invalid.",
        "properties": {
          "message": {
            "type": "string",
            "example": "Unauthenticated."
          }
        }
      },
      "HubB2bResponse": {
        "type": "object",
        "description": "Additive machine envelope. Wire key hub_b2b_response (user name hub_b2b_response_code[v1] maps here). Sibling of Laravel data wrap. Version 1. Message locale is pl. Source of truth: Masstrade\\Contracts\\B2b\\Hub\\HubB2bResponseV1 plus HubB2bResponseCode / HubB2bResponseReason. Hub App\\Types\\* are 1:1 runtime mirrors until composer update masstrade/contracts (Draft 45 L5).",
        "required": [
          "version",
          "code",
          "reason",
          "message"
        ],
        "properties": {
          "version": {
            "type": "integer",
            "enum": [
              1
            ],
            "description": "Envelope version. Always 1 in this pass."
          },
          "code": {
            "type": "string",
            "description": "Closed enum. Masstrade\\Contracts\\B2b\\Hub\\HubB2bResponseCode (hub App\\Types\\HubB2bResponseCode is a 1:1 mirror).",
            "enum": [
              "CART_READ",
              "CART_UPDATED",
              "CART_CLEARED",
              "CART_LOADED",
              "CART_BLOCKED",
              "CART_CONFLICT",
              "CART_UNAVAILABLE"
            ]
          },
          "reason": {
            "type": "string",
            "description": "snake_case token. Masstrade\\Contracts\\B2b\\Hub\\HubB2bResponseReason (hub App\\Types\\HubB2bResponseReason is a 1:1 mirror). PUT /api/v2/cart rejects with code CART_UNAVAILABLE: out_of_stock (pid not in cart), no_more_stock (pid already has quantity) or sale_step_invalid (quantity is not a multiple of the channel's smallest enabled purchase unit - cart.messages[].code 400, not waivable by cart.clientAddUnavailable / cart.providerAddUnavailable). Do not parse message.",
            "enum": [
              "cart_loaded",
              "cart_updated",
              "cart_cleared",
              "loaded_from_order",
              "line_added",
              "line_set",
              "line_removed",
              "line_cleared",
              "account_blocked",
              "multiple_sellers",
              "product_not_in_offer",
              "out_of_stock",
              "no_more_stock",
              "transport_set",
              "payment_set",
              "comment_set",
              "address_set",
              "sale_step_invalid"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human Polish for display. Not a classifier. Stock pair: Brak produktu w magazynie (out_of_stock) / Brak więcej (no_more_stock)."
          }
        }
      },
      "CartJsonResponse": {
        "type": "object",
        "description": "Cart HTTP JSON. Old clients read data.context. Additive hub_b2b_response sits next to data. context keys are CartContextResource (Draft 61 phase 2).",
        "required": [
          "data",
          "hub_b2b_response"
        ],
        "properties": {
          "data": {
            "type": "object",
            "description": "Laravel JsonResource wrap of CartContextResource.",
            "properties": {
              "context": {
                "$ref": "#/components/schemas/CartContext"
              }
            }
          },
          "hub_b2b_response": {
            "$ref": "#/components/schemas/HubB2bResponse"
          }
        }
      },
      "CartV2Dto": {
        "type": "object",
        "description": "Masstrade\\Contracts\\B2b\\Hub\\Cart\\CartV2Dto. No Laravel data wrap. Machine result is nested response, not sibling hub_b2b_response.",
        "required": [
          "sid",
          "version",
          "account_id",
          "response",
          "cart"
        ],
        "properties": {
          "sid": {
            "type": "string"
          },
          "version": {
            "type": "integer",
            "enum": [
              2
            ]
          },
          "account_id": {
            "type": "integer",
            "minimum": 1
          },
          "response": {
            "$ref": "#/components/schemas/HubB2bResponse"
          },
          "cart": {
            "$ref": "#/components/schemas/CartV2Context"
          }
        }
      },
      "ClaimsV2": {
        "type": "object",
        "description": "Masstrade\\Contracts\\B2b\\Claims\\ClaimsV2Dto. Buyer list for one contractor. Same JSON as ERP GET /b2b/v2/claims. No Laravel data wrap.",
        "required": [
          "sid",
          "version",
          "account_id",
          "customer_id",
          "status",
          "claims",
          "meta"
        ],
        "properties": {
          "sid": {
            "type": "string"
          },
          "version": {
            "type": "integer",
            "enum": [
              2
            ]
          },
          "account_id": {
            "type": "integer"
          },
          "customer_id": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "closed",
              "all"
            ]
          },
          "claims": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClaimV2"
            }
          },
          "meta": {
            "$ref": "#/components/schemas/ClaimsMetaV2"
          }
        }
      },
      "ClaimV2": {
        "type": "object",
        "description": "Masstrade\\Contracts\\B2b\\Claims\\ClaimV2Dto. Slim row.",
        "required": [
          "id",
          "number",
          "group",
          "status",
          "open",
          "created_at",
          "updated_at",
          "public_url"
        ],
        "properties": {
          "id": {
            "type": "integer"
          },
          "number": {
            "type": "string"
          },
          "group": {
            "type": "integer",
            "enum": [
              1,
              2,
              3
            ]
          },
          "status": {
            "type": "integer",
            "description": "Last status: 0 reported, 1 completed, 2 in progress, 10 derived overdue (never stored), 100 cancelled.",
            "enum": [
              0,
              1,
              2,
              10,
              100
            ]
          },
          "open": {
            "type": "boolean"
          },
          "overdue": {
            "type": "boolean",
            "description": "Unresolved and older than the account threshold - ERP decides, consumers render. Optional: absent from older writers."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "public_url": {
            "type": "string"
          }
        }
      },
      "ClaimsMetaV2": {
        "type": "object",
        "required": [
          "total",
          "per_page",
          "offset"
        ],
        "properties": {
          "total": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "CartV2Context": {
        "type": "object",
        "description": "ContextV2Dto. Clean names vs CartContextResource (pid not cart_pid, transports not allowed_transports). Off this tree until named: products_reservations, order_discount.",
        "required": [
          "is_guest",
          "products",
          "summary",
          "selected",
          "transports",
          "payments",
          "addresses",
          "messages"
        ],
        "properties": {
          "is_guest": {
            "type": "boolean"
          },
          "expires_in_seconds": {
            "type": "integer"
          },
          "expires_at": {
            "type": "string",
            "nullable": true
          },
          "min_order_price": {
            "type": "number"
          },
          "min_order_price_is_brutto": {
            "type": "boolean"
          },
          "default_address_id": {
            "type": "integer",
            "description": "-1 when none"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2Item"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/CartV2Summary"
          },
          "selected": {
            "$ref": "#/components/schemas/CartV2Selected"
          },
          "transports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2Transport"
            }
          },
          "payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2Payment"
            }
          },
          "addresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2Address"
            }
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2Message"
            }
          },
          "user": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CartV2User"
              }
            ]
          },
          "supervisor": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CartV2User"
              }
            ]
          },
          "assigned_supervisor": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CartV2Seller"
              }
            ]
          }
        }
      },
      "CartV2Item": {
        "type": "object",
        "description": "ItemV2Dto. v1 cart_pid → pid, acid → account_id, price_nodiscb → price_brutto_no_discount. sale_options carries the line's purchase units — the same keys and the same computation as CartItem.sale_options on v1.",
        "required": [
          "pid",
          "quantity",
          "title"
        ],
        "properties": {
          "pid": {
            "type": "integer"
          },
          "quantity": {
            "type": "number"
          },
          "added_at": {
            "type": "string"
          },
          "price_brutto": {
            "type": "number"
          },
          "price_netto": {
            "type": "number"
          },
          "price_vat": {
            "type": "number"
          },
          "price_disc": {
            "type": "number"
          },
          "price_brutto_no_discount": {
            "type": "number"
          },
          "title": {
            "type": "string"
          },
          "account_id": {
            "type": "integer"
          },
          "sku": {
            "type": "string"
          },
          "sale_options": {
            "type": "array",
            "description": "Unit prices of this line (SaleOptionV2Dto). Same shape as CartItem.sale_options on v1: unit (piece / package / layer / pallet), pieces (how many pieces the unit holds) and the unit's price_net / price_gross / price_net_before_discount / price_gross_before_discount, computed from the price THIS line holds. Empty when the channel sells pieces or the product carries no units, so the key is always there; a missing price is null. Display only - the cart total stays quantity x piece price and orders go in pieces.",
            "items": {
              "type": "object",
              "required": [
                "unit",
                "pieces"
              ],
              "properties": {
                "unit": {
                  "type": "string",
                  "enum": [
                    "piece",
                    "layer",
                    "pallet"
                  ]
                },
                "pieces": {
                  "type": "integer",
                  "minimum": 1
                },
                "price_net": {
                  "type": "number",
                  "nullable": true
                },
                "price_gross": {
                  "type": "number",
                  "nullable": true
                },
                "price_net_before_discount": {
                  "type": "number",
                  "nullable": true
                },
                "price_gross_before_discount": {
                  "type": "number",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "CartV2Selected": {
        "type": "object",
        "description": "SelectedV2Dto. address_id is -1 when none (v1 transport_address_id).",
        "properties": {
          "transport_key": {
            "type": "string"
          },
          "payment_type": {
            "type": "string"
          },
          "comment": {
            "type": "string"
          },
          "address_id": {
            "type": "integer"
          }
        }
      },
      "CartV2Summary": {
        "type": "object",
        "description": "SummaryV2Dto",
        "properties": {
          "total_netto": {
            "type": "number"
          },
          "total_brutto": {
            "type": "number"
          },
          "total_brutto_with_transport": {
            "type": "number"
          },
          "transport": {
            "type": "number"
          },
          "vat_type": {
            "type": "string"
          }
        }
      },
      "CartV2Transport": {
        "type": "object",
        "description": "TransportV2Dto. v1 is_free_transport → is_free, cd_aid → pricing_id, rodzaj → kind.",
        "properties": {
          "transport_key": {
            "type": "string"
          },
          "transport_name": {
            "type": "string"
          },
          "is_free": {
            "type": "boolean"
          },
          "price": {
            "type": "number"
          },
          "local": {
            "type": "boolean"
          },
          "shipping_time": {
            "type": "string"
          },
          "type": {
            "type": "string"
          },
          "module": {
            "type": "string"
          },
          "submodule": {
            "type": "string"
          },
          "active": {
            "type": "boolean"
          },
          "pricing_id": {
            "type": "integer"
          },
          "currency": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "thumb_url": {
            "type": "string"
          }
        }
      },
      "CartV2Payment": {
        "type": "object",
        "description": "PaymentV2Dto. v1 payments_key → key.",
        "properties": {
          "key": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "details": {
            "type": "string"
          },
          "icon": {
            "type": "string"
          }
        }
      },
      "CartV2Message": {
        "type": "object",
        "description": "MessageV2Dto. Numeric code 200/403/406/409 — not HubB2bResponseCode.",
        "properties": {
          "code": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "pid": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "CartV2User": {
        "type": "object",
        "description": "UserV2Dto. No UserResource duplicate user_id.",
        "properties": {
          "is_guest": {
            "type": "boolean"
          },
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "role": {
            "type": "string"
          },
          "mt_customer_id": {
            "type": "integer"
          },
          "remote_blocked": {
            "type": "boolean"
          },
          "user_currency": {
            "type": "string"
          },
          "locale": {
            "type": "string"
          },
          "lang": {
            "type": "string"
          }
        }
      },
      "CartV2Seller": {
        "type": "object",
        "description": "SellerV2Dto",
        "properties": {
          "email": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          }
        }
      },
      "CartV2Address": {
        "type": "object",
        "description": "AddressV2Dto. Off until named: assigned_users, mt_symkar, timestamps.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "surname": {
            "type": "string"
          },
          "postcode": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "number_1": {
            "type": "string"
          },
          "number_2": {
            "type": "string"
          },
          "country_id": {
            "type": "integer"
          },
          "province_id": {
            "type": "integer"
          },
          "company_name": {
            "type": "string"
          },
          "company_vat": {
            "type": "string"
          },
          "is_company": {
            "type": "boolean"
          },
          "is_default": {
            "type": "boolean"
          },
          "phones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2AddressPhone"
            }
          },
          "emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartV2AddressEmail"
            }
          }
        }
      },
      "CartV2AddressPhone": {
        "type": "object",
        "description": "AddressPhoneV2Dto",
        "properties": {
          "number": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "is_fax": {
            "type": "boolean"
          }
        }
      },
      "CartV2AddressEmail": {
        "type": "object",
        "description": "AddressEmailV2Dto",
        "properties": {
          "mail": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          }
        }
      },
      "AddressPhone": {
        "type": "object",
        "description": "PhoneResource calls parent::toArray. Eloquent Phone minus $hidden (id, address_id). App\\Models\\Phone.",
        "properties": {
          "number": {
            "type": "string"
          },
          "is_fax": {
            "type": "boolean"
          },
          "is_default": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AddressEmail": {
        "type": "object",
        "description": "EmailResource calls parent::toArray. Eloquent Email minus $hidden (id, address_id). App\\Models\\Email.",
        "properties": {
          "mail": {
            "type": "string"
          },
          "is_default": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string",
            "nullable": true
          },
          "updated_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AddressResource": {
        "type": "object",
        "description": "App\\Http\\Resources\\AddressResource. Session checkout and signed-in buyer addresses on /api/customer/addresses*.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "b2b_customer_id": {
            "type": "integer",
            "description": "Address.user_id"
          },
          "name": {
            "type": "string"
          },
          "surname": {
            "type": "string"
          },
          "postcode": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "number_1": {},
          "number_2": {},
          "country_id": {
            "type": "integer"
          },
          "province_id": {
            "type": "integer"
          },
          "company_name": {
            "type": "string"
          },
          "company_vat": {
            "type": "string"
          },
          "is_company": {
            "type": "boolean"
          },
          "is_default": {
            "type": "boolean"
          },
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "phones": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddressPhone"
            }
          },
          "emails": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddressEmail"
            }
          },
          "assigned_users": {
            "type": "array",
            "description": "Address::getAssignedUserIDs()",
            "items": {
              "type": "integer"
            }
          },
          "mt_symkar": {
            "type": "string"
          },
          "remote_account_id": {
            "type": "integer",
            "description": "b2bCustomer.remote_account_id or 0"
          }
        }
      },
      "AddressResourceData": {
        "type": "object",
        "description": "Laravel JsonResource wrap of one AddressResource.",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/AddressResource"
          }
        }
      },
      "AddressResourceCollection": {
        "type": "object",
        "description": "Laravel Resource collection wrap of AddressResource.",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AddressResource"
            }
          }
        }
      },
      "AddressWrite": {
        "type": "object",
        "description": "CustomerAddressController::put $request->validate(). phones and emails are comma-separated strings, not arrays.",
        "required": [
          "name",
          "surname",
          "postcode",
          "city",
          "street",
          "number_1",
          "country_id",
          "province_id",
          "is_company"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "surname": {
            "type": "string"
          },
          "postcode": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "street": {
            "type": "string"
          },
          "number_1": {
            "type": "string"
          },
          "number_2": {
            "type": "string"
          },
          "country_id": {
            "type": "integer"
          },
          "province_id": {
            "type": "integer"
          },
          "company_name": {
            "type": "string"
          },
          "company_vat": {
            "type": "string"
          },
          "is_company": {
            "type": "boolean"
          },
          "phones": {
            "type": "string",
            "description": "Comma-separated numbers"
          },
          "emails": {
            "type": "string",
            "nullable": true,
            "description": "Comma-separated addresses"
          },
          "apiname": {
            "type": "string"
          },
          "idca_is_vat_ue": {
            "type": "boolean",
            "nullable": true
          },
          "idca_is_running_vat": {
            "type": "boolean",
            "nullable": true
          }
        }
      },
      "CartItem": {
        "type": "object",
        "description": "App\\Http\\Resources\\CartItemResource.",
        "properties": {
          "cart_pid": {},
          "cart_quantity": {},
          "cart_added_date": {
            "type": "string"
          },
          "price_brutto": {},
          "price_netto": {},
          "price_vat": {},
          "price_disc": {},
          "price_nodiscb": {
            "description": "CartItem.mt_price_nodisc"
          },
          "prod_lang_title": {},
          "acid": {
            "description": "CartItem.mt_account_id"
          },
          "sku": {
            "type": "string"
          },
          "sale_options": {
            "type": "array",
            "description": "Unit prices of this line (CartItemResource + App\\Support\\SaleUnits::unitPricesForItem). Empty when the channel sells pieces or the product carries no sale_options. Each entry: unit (piece / package / layer / pallet), pieces (how many pieces the unit holds) and the unit's price_net / price_gross / price_net_before_discount / price_gross_before_discount, computed as pieces x the price THIS line holds (mt_price_netto / mt_price_brutto), so a quantity discount is already in. Display only: the cart total stays quantity x piece price and orders go in pieces. Both carts carry it - the same units, the same keys and the same computation are on CartV2Item (/api/v2/cart).",
            "items": {
              "type": "object",
              "required": [
                "unit",
                "pieces"
              ],
              "properties": {
                "unit": {
                  "type": "string",
                  "enum": [
                    "piece",
                    "layer",
                    "pallet"
                  ]
                },
                "pieces": {
                  "type": "integer",
                  "minimum": 1
                },
                "price_net": {
                  "type": "number",
                  "nullable": true
                },
                "price_gross": {
                  "type": "number",
                  "nullable": true
                },
                "price_net_before_discount": {
                  "type": "number",
                  "nullable": true
                },
                "price_gross_before_discount": {
                  "type": "number",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "CartSummary": {
        "type": "object",
        "description": "App\\Http\\Resources\\SummaryResource.",
        "properties": {
          "total_netto": {},
          "total_brutto": {},
          "total_brutto_with_transport": {},
          "transport": {},
          "vat_type": {}
        }
      },
      "CartSelected": {
        "type": "object",
        "description": "CartContextResource selected block (cart columns).",
        "properties": {
          "transport_key": {},
          "payment_type": {},
          "comment": {},
          "transport_address_id": {
            "description": "cart.address_id, or -1 when address_id is 0"
          }
        }
      },
      "CartTransport": {
        "type": "object",
        "description": "App\\Http\\Resources\\TransportResource (AllowedTransport).",
        "properties": {
          "transport_key": {
            "type": "string"
          },
          "transport_name": {
            "type": "string"
          },
          "is_free_transport": {
            "type": "boolean"
          },
          "price": {},
          "local": {},
          "shipping_time": {},
          "type": {},
          "module": {},
          "submodule": {},
          "active": {},
          "cd_aid": {},
          "currency": {},
          "rodzaj": {},
          "thumb_url": {}
        }
      },
      "CartPayment": {
        "type": "object",
        "description": "App\\Http\\Resources\\PaymentResource.",
        "properties": {
          "payments_key": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "details": {},
          "icon": {}
        }
      },
      "CartUser": {
        "type": "object",
        "description": "App\\Http\\Resources\\UserResource (deprecated class; still emitted).",
        "properties": {
          "is_guest": {
            "type": "boolean"
          },
          "id": {},
          "user_id": {
            "description": "Same as id"
          },
          "email": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "usertype": {},
          "mt_customer_id": {},
          "b2b_group_id": {},
          "role": {
            "type": "string"
          },
          "allowed_mt_accounts": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "remote_balance": {},
          "remote_blocked": {
            "type": "boolean"
          },
          "password_changed_count": {},
          "user_contact_name": {
            "type": "string"
          },
          "user_phone": {
            "type": "string"
          },
          "user_currency": {
            "type": "string"
          },
          "locale": {
            "type": "string"
          },
          "lang": {
            "type": "string"
          }
        }
      },
      "HubUser": {
        "type": "object",
        "description": "GET /api/user — Eloquent user array plus ERP buyer credit fields.",
        "properties": {
          "id": {
            "type": "integer"
          },
          "email": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "idc_id": {
            "type": "integer"
          },
          "locale": {
            "type": "string"
          },
          "user_currency": {
            "type": "string"
          },
          "logged_as_user_id": {
            "type": "integer",
            "nullable": true
          },
          "mt_customer_id": {
            "type": "integer",
            "description": "ERP contractor PK (`bm_customer` / `mt_customer_id`) for the requested shop; -1 when it cannot be resolved"
          },
          "remote_account_id": {
            "type": "integer",
            "description": "ERP account of the shop the resolved contractor belongs to; -1 when unresolved"
          },
          "sandbox": {
            "type": "boolean",
            "description": "`users.mt_sandbox`"
          },
          "idc_limit": {
            "type": "number",
            "format": "float",
            "description": "ERP bm_customer.idc_limit; 0 when unknown"
          },
          "idc_limit_available": {
            "type": "number",
            "format": "float"
          },
          "idc_limit_exceeded": {
            "type": "number",
            "format": "float"
          }
        },
        "required": [
          "id",
          "email"
        ]
      },
      "CartSeller": {
        "type": "object",
        "description": "App\\Http\\Resources\\SellerResource.",
        "properties": {
          "email": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "phone": {
            "type": "string"
          }
        }
      },
      "CartMessage": {
        "type": "object",
        "description": "CartContext messages set by CartController. Numeric code is 200/403/406/409 (not hub_b2b_response.code). Optional pid on some line rows.",
        "properties": {
          "code": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "pid": {
            "type": "integer"
          }
        }
      },
      "CartContext": {
        "type": "object",
        "description": "Keys from CartContextResource::toArray inner context. cart_expire_* only when request apiname is not masstrade_portal. order_discount is [] on a session cart; signed-in cart uses Cart::getOrderDiscount() (item keys not listed). products_reservations is the raw array from CartContext::getProductReservations() (item keys not listed).",
        "required": [
          "is_guest",
          "products",
          "summary",
          "selected",
          "allowed_transports",
          "allowed_payments",
          "allowed_addresses",
          "default_address_id",
          "messages",
          "order_discount",
          "min_order_price",
          "min_order_price_type",
          "request_account_id"
        ],
        "properties": {
          "is_guest": {
            "type": "boolean"
          },
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartItem"
            }
          },
          "summary": {
            "$ref": "#/components/schemas/CartSummary"
          },
          "selected": {
            "$ref": "#/components/schemas/CartSelected"
          },
          "allowed_transports": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartTransport"
            }
          },
          "allowed_payments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartPayment"
            }
          },
          "allowed_addresses": {
            "description": "AddressResource collection when a user exists; otherwise [].",
            "oneOf": [
              {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/AddressResource"
                }
              },
              {
                "type": "array",
                "maxItems": 0
              }
            ]
          },
          "default_address_id": {
            "type": "integer",
            "description": "First allowed address id, or -1"
          },
          "user": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CartUser"
              }
            ]
          },
          "user_supervisor": {
            "nullable": true,
            "description": "UserResource when real user first role is seller; else null.",
            "allOf": [
              {
                "$ref": "#/components/schemas/CartUser"
              }
            ]
          },
          "products_reservations": {
            "type": "array",
            "description": "Raw CartContext::getProductReservations(). Item keys not named in hub code."
          },
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartMessage"
            }
          },
          "assigned_supervisor": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/CartSeller"
              }
            ]
          },
          "order_discount": {
            "type": "array",
            "description": "[] on session cart; otherwise Cart::getOrderDiscount() (keys not listed)."
          },
          "min_order_price": {
            "type": "integer"
          },
          "min_order_price_type": {
            "type": "integer",
            "description": "account_settings.config_remote orders.minPriceType.string cast to int"
          },
          "request_account_id": {},
          "cart_expire_seconds": {
            "type": "integer",
            "description": "Omitted when apiname is masstrade_portal"
          },
          "cart_expire_at": {
            "type": "string",
            "description": "Omitted when apiname is masstrade_portal"
          }
        }
      },
      "CartContextData": {
        "type": "object",
        "description": "Laravel JsonResource wrap of CartContextResource without hub_b2b_response. Used by GET /api/guest_orders/context (no shared envelope emit path).",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "object",
            "required": [
              "context"
            ],
            "properties": {
              "context": {
                "$ref": "#/components/schemas/CartContext"
              }
            }
          }
        }
      },
      "OrderPaymentStatus": {
        "type": "object",
        "description": "App\\Http\\Resources\\OrderPaymentStatusResource.",
        "properties": {
          "status": {},
          "created_at": {
            "type": "string"
          }
        }
      },
      "OrderPayment": {
        "type": "object",
        "description": "App\\Http\\Resources\\OrderPaymentResource.",
        "properties": {
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "statuses": {
            "type": "array",
            "description": "OrderPaymentStatusResource collection reversed.",
            "items": {
              "$ref": "#/components/schemas/OrderPaymentStatus"
            }
          }
        }
      },
      "OrderResource": {
        "type": "object",
        "description": "App\\Http\\Resources\\OrderResource. address is the Eloquent Address on the order (not AddressResource). seller_data is sellerAccount->getAddressData() or [] (keys not listed). discount is order_discount (keys not listed).",
        "properties": {
          "id": {},
          "hash": {
            "type": "string"
          },
          "b2b_customer_id": {},
          "user_id": {},
          "user_email": {
            "type": "string"
          },
          "mt_apiname": {},
          "mt_transport_key": {},
          "mt_payment_type": {},
          "address_id": {},
          "comment": {},
          "shipping_number": {
            "type": "string"
          },
          "shipping_url": {
            "type": "string"
          },
          "ip": {},
          "currency_dig3": {},
          "total_without_tax": {},
          "total_with_tax": {},
          "total_with_tax_transport": {},
          "address": {
            "description": "Eloquent Address on the order, not AddressResource.",
            "type": "object"
          },
          "mt_account_id": {},
          "mt_sandbox": {},
          "created_at": {
            "type": "string"
          },
          "updated_at": {
            "type": "string"
          },
          "status": {},
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CartItem"
            }
          },
          "payment": {
            "nullable": true,
            "allOf": [
              {
                "$ref": "#/components/schemas/OrderPayment"
              }
            ]
          },
          "pay_url": {},
          "created_by_supervisor": {},
          "seller_data": {
            "description": "sellerAccount->getAddressData() or []. Keys not listed."
          },
          "from_platform": {},
          "from_os": {},
          "from_client_version": {},
          "guest": {},
          "discount": {
            "description": "Order.order_discount. Keys not listed."
          },
          "vat_type": {}
        }
      },
      "OrderResourceData": {
        "type": "object",
        "description": "Laravel JsonResource wrap of OrderResource. No hub_b2b_response (no shared emit path).",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "$ref": "#/components/schemas/OrderResource"
          }
        }
      },
      "OrderCountResponse": {
        "type": "object",
        "description": "GET /api/count for the buyer storefront. orders is a list of OrderResource (or Laravel paginator {data}). No invoice URL today.",
        "required": [
          "orders",
          "orders_count"
        ],
        "properties": {
          "orders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderResource"
            }
          },
          "orders_count": {
            "type": "integer",
            "minimum": 0
          }
        }
      },
      "OpsLogEvent": {
        "type": "object",
        "description": "One row from the logs table (LogManager).",
        "properties": {
          "id": {
            "type": "integer"
          },
          "type": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          },
          "data": {
            "type": "string"
          },
          "apiname": {
            "type": "string"
          },
          "user_id": {
            "type": "integer"
          },
          "logged_as_user_id": {
            "type": "integer"
          },
          "extra": {},
          "created_at": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "OpsLogFile": {
        "type": "object",
        "description": "One *.log file in storage/logs.",
        "properties": {
          "name": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "mtime": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "OpsLogsResponse": {
        "type": "object",
        "description": "GET /api/ops/logs. source=table has events. source=files has file tail. fallback=true when auto switched after a table error.",
        "required": [
          "source",
          "fallback"
        ],
        "properties": {
          "source": {
            "type": "string",
            "enum": [
              "table",
              "files"
            ]
          },
          "fallback": {
            "type": "boolean"
          },
          "fallback_reason": {
            "type": "string",
            "nullable": true
          },
          "limit": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpsLogEvent"
            }
          },
          "files": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpsLogFile"
            }
          },
          "file": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "content": {
            "type": "string"
          }
        }
      }
    },
    "responses": {
      "CartOk": {
        "description": "OK. Existing cart context under data. Additive hub_b2b_response sibling of data. Cart business 403/406/409 stay this HTTP 200: they appear in data.context.messages[].code and classify hub_b2b_response (CART_BLOCKED, CART_UNAVAILABLE, CART_CONFLICT). They are not separate HTTP statuses.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CartJsonResponse"
            }
          }
        }
      },
      "CartV2Ok": {
        "description": "OK. CartV2Dto at the root. Nested response is HubB2bResponse. Cart business 403/406/409 stay this HTTP 200 (cart.messages[].code + response CART_BLOCKED / CART_UNAVAILABLE / CART_CONFLICT). PUT stock: response.reason is out_of_stock or no_more_stock from whether cart.products already contains cart_pid.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CartV2Dto"
            }
          }
        }
      }
    }
  }
}
