{
  "openapi": "3.0.3",
  "info": {
    "title": "ePostak Enterprise API",
    "description": "Enterprise REST API pre e-fakturaciu cez Peppol. OAuth 2.0 JWT auth (vymeňte `sk_live_*`/`sk_int_*` API kľúč za 15 min JWT cez `POST /api/v1/auth/token`). JSON aj XML mode, multi-tenant, idempotency support, OCR, webhooks, 10-year AS4 envelope archive.\n\nDocs: https://epostak.sk/api/docs/enterprise",
    "version": "1.5.1",
    "contact": {
      "email": "info@epostak.sk",
      "url": "https://epostak.sk"
    }
  },
  "servers": [
    {
      "url": "https://epostak.sk/api/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Documents",
      "description": "Odosielanie dokumentov cez Peppol"
    },
    {
      "name": "Inbox",
      "description": "Prijate dokumenty"
    },
    {
      "name": "Account",
      "description": "Informacie o ucte a firme"
    },
    {
      "name": "Extraction",
      "description": "OCR extrakcia faktur z PDF/obrazkov (Enterprise only)"
    },
    {
      "name": "Webhooks",
      "description": "Sprava webhook odberov (Enterprise only)"
    },
    {
      "name": "Firms",
      "description": "Sprava firiem a Peppol identifikatorov"
    },
    {
      "name": "Peppol",
      "description": "SMP lookup a Peppol directory"
    },
    {
      "name": "Reporting",
      "description": "Statistiky a reporty"
    },
    {
      "name": "OAuth",
      "description": "OAuth 2.0 token exchange"
    },
    {
      "name": "Auth",
      "description": "API key introspection and rotation"
    },
    {
      "name": "Validation",
      "description": "Public UBL/Peppol validator (no auth)"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    }
  ],
  "paths": {
    "/auth/token": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Mint a JWT access token (OAuth 2.0 client_credentials)",
        "description": "Exchange a `sk_live_*` or `sk_int_*` API key for a short-lived JWT access token + refresh token.\n\n**Body** (JSON or `application/x-www-form-urlencoded`):\n- `grant_type`: must be `client_credentials`\n- `client_id`: the client_id shown with the key; it must match the API key row UUID or displayed key prefix and must not be the secret\n- `client_secret`: the full `sk_live_*` or `sk_int_*` key\n- `scope` (optional): space-separated subset of the key's allowed scopes\n\n**Response** (RFC 6749):\n```json\n{\n  \"access_token\": \"eyJhbGciOiJSUzI1NiIs...\",\n  \"token_type\": \"Bearer\",\n  \"expires_in\": 900,\n  \"refresh_token\": \"rt_...\",\n  \"scope\": \"documents:send documents:read\"\n}\n```\n\n**Errors** use Peppol error envelope: `{ error: { category, code, message, retryable, correlation_id, details? } }`.\n\nAccount lockout: 5 failed attempts within 10 min on the same `client_id` returns 423.\n\nThis endpoint is the canonical Enterprise URL; `/sapi/v1/auth/token` is the SAPI-spec equivalent and shares the same handler.",
        "operationId": "authToken",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JwtTokenRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/JwtTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Access token + refresh token issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JwtTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid grant_type or malformed body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid client credentials",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Requested scope(s) not allowed by key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          },
          "423": {
            "description": "Account locked after 5 failed attempts",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/renew": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Refresh access token using a refresh token",
        "description": "Rotate a refresh token: returns a new access JWT (15 min) + a new refresh token (30 days). The old refresh token is invalidated.\n\n**Body**:\n```json\n{ \"grant_type\": \"refresh_token\", \"refresh_token\": \"rt_...\" }\n```\n\nRefresh tokens are stateless JWTs verified server-side via Redis presence — once consumed, they cannot be reused (replay → 401).",
        "operationId": "authRenew",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JwtRenewRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New access + refresh token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JwtTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Missing or malformed refresh_token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Refresh token invalid, expired, or already consumed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/revoke": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Revoke an access or refresh token (RFC 7009)",
        "description": "Idempotent — always returns 200. Revoking an access token adds its `jti` to the Redis blocklist for the remainder of its lifetime. Revoking a refresh token deletes its server-side state.\n\n**Body**:\n```json\n{ \"token\": \"<jwt or refresh token>\", \"token_type_hint\": \"access_token\" | \"refresh_token\" }\n```\n\n`token_type_hint` is optional; the server detects the type automatically (`eyJ` prefix → JWT, `rt_` prefix → refresh).",
        "operationId": "authRevoke",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JwtRevokeRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token revoked (or was already invalid — idempotent)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Missing token field",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/token/status": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Inspect current JWT — expiry, refresh hint, scope",
        "description": "Returns metadata about the JWT presented in `Authorization: Bearer`. Useful for SDKs to decide whether to renew before making a long-running request.",
        "operationId": "authTokenStatus",
        "responses": {
          "200": {
            "description": "Token introspection",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "active": {
                      "type": "boolean"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "expires_in_seconds": {
                      "type": "integer"
                    },
                    "should_refresh": {
                      "type": "boolean",
                      "description": "True if within the last 25% of the access token's TTL"
                    },
                    "refresh_recommended_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "firm_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    },
                    "integrator_id": {
                      "type": "string",
                      "format": "uuid",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid JWT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/status": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "API key introspection",
        "description": "Returns metadata about the calling API key: permissions, linked firm, plan, rate-limit window. Cheap health-check alternative to `/account`.",
        "operationId": "authStatus",
        "responses": {
          "200": {
            "description": "Introspection OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthStatusResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/rotate-secret": {
      "post": {
        "tags": [
          "Auth"
        ],
        "summary": "Rotate the current API key",
        "description": "Issues a new API key with the same name/permissions and marks the old one inactive immediately. The new key is returned ONCE in the response. Integrator keys (sk_int_*) are not rotatable via this endpoint — use the integrator dashboard.",
        "operationId": "rotateApiKey",
        "responses": {
          "200": {
            "description": "Key rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RotateSecretResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Integrator keys cannot be rotated via this endpoint, or enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "API key not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/auth/ip-allowlist": {
      "get": {
        "tags": [
          "Auth"
        ],
        "summary": "Get IP allowlist for current key",
        "description": "Returns the IP allowlist for the API key authenticating this request. Empty array → no IP restriction (any caller IP allowed). Per-key, not per-firm — `sk_live_*` and `sk_int_*` allowlists live in their own tables.\n\n**Requires `account:read` scope and `api-enterprise` plan.**",
        "operationId": "getIpAllowlist",
        "responses": {
          "200": {
            "description": "Current allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ip_allowlist": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not allow this endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "put": {
        "tags": [
          "Auth"
        ],
        "summary": "Replace IP allowlist for current key",
        "description": "Replaces the IP allowlist for the API key authenticating this request. Each entry is either a bare IP (v4 or v6) or a CIDR `addr/prefix`. Max 50 entries. Empty array clears the restriction. Cache is busted so changes are effective on the next request.\n\n**Requires `account:read` scope and `api-enterprise` plan.**",
        "operationId": "updateIpAllowlist",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ip_allowlist"
                ],
                "properties": {
                  "ip_allowlist": {
                    "type": "array",
                    "maxItems": 50,
                    "items": {
                      "type": "string",
                      "example": "203.0.113.42"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated allowlist",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ip_allowlist": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid entry or too many entries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not allow this endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "tags": [
          "OAuth"
        ],
        "summary": "OAuth 2.0 token exchange",
        "description": "**Public endpoint at `https://epostak.sk/api/oauth/token`** (NOT under `/api/v1/`). Exchange an authorization code for a new `sk_int_*` client secret. The returned secret is not a bearer token; exchange it via `POST /api/v1/auth/token` to mint the short-lived JWT used on `/api/v1/*` routes. Supports PKCE via `code_verifier`.",
        "operationId": "oauthTokenExchange",
        "servers": [
          {
            "url": "https://epostak.sk/api",
            "description": "Public base (no /v1 prefix)"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OAuthTokenRequest"
              },
              "example": {
                "grant_type": "authorization_code",
                "code": "auth_code_here",
                "client_id": "client_id_here",
                "client_secret": "client_secret_here",
                "redirect_uri": "https://example.com/callback",
                "code_verifier": "pkce_verifier_here"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Integrator client secret issued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthTokenResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid grant or request parameters (RFC 6749 error body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid client credentials (RFC 6749 error body)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OAuthErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Informacie o ucte",
        "description": "Vrati zakladne informacie o firme, stav Peppol registracie, aktivny plan a statistiky odoslanych/prijatych dokumentov.",
        "operationId": "getAccount",
        "responses": {
          "200": {
            "description": "Informacie o ucte",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AccountResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Firma nenajdena",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/licenses/info": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Plan and current-period usage",
        "description": "Returns the firm's plan tier, current billing period usage (outbound + inbound API counts), overage so far, quota remaining, estimated month cost, and pricing tiers. Use for billing dashboards and quota warnings.\n\n**Requires `account:read` scope and `api-enterprise` plan.**",
        "operationId": "getLicenseInfo",
        "responses": {
          "200": {
            "description": "Plan + usage snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "firm": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "sandbox": {
                          "type": "boolean"
                        }
                      }
                    },
                    "plan": {
                      "type": "object",
                      "properties": {
                        "code": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "active": {
                          "type": "boolean"
                        },
                        "expiresAt": {
                          "type": "string",
                          "format": "date-time",
                          "nullable": true
                        },
                        "daysUntilExpiry": {
                          "type": "integer",
                          "nullable": true
                        },
                        "gracePeriodActive": {
                          "type": "boolean"
                        },
                        "monthlyBase": {
                          "type": "number"
                        },
                        "outboundLimit": {
                          "type": "integer",
                          "nullable": true
                        },
                        "overageRate": {
                          "type": "number",
                          "nullable": true
                        },
                        "inboundApiRate": {
                          "type": "number",
                          "nullable": true
                        },
                        "hasApiAccess": {
                          "type": "boolean"
                        }
                      }
                    },
                    "usage": {
                      "type": "object",
                      "properties": {
                        "period": {
                          "type": "string",
                          "example": "2026-04"
                        },
                        "nextResetAt": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "outboundCount": {
                          "type": "integer"
                        },
                        "inboundApiCount": {
                          "type": "integer"
                        },
                        "overageAmount": {
                          "type": "number"
                        },
                        "quotaRemaining": {
                          "type": "integer",
                          "nullable": true
                        },
                        "usagePercent": {
                          "type": "number",
                          "nullable": true
                        },
                        "estimatedMonthCost": {
                          "type": "number"
                        }
                      }
                    },
                    "pricing": {
                      "type": "object",
                      "properties": {
                        "currency": {
                          "type": "string",
                          "example": "EUR"
                        },
                        "model": {
                          "type": "string",
                          "example": "tiered"
                        },
                        "outboundTiers": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "inboundApiTiers": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not allow this endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Firm not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/integrator/licenses/info": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Aggregate plan + current-period usage for an integrator",
        "description": "Returns the integrator plan and current-period usage **aggregated across every firm the integrator manages**. Tier rates apply to the AGGREGATE counts (not per-firm) — a 100-firm × 50-doc integrator lands in tier 2–3, not tier 1 like a standalone firm would. Volumes above `contactThreshold` (5 000) have no published rate (`exceedsAutoTier` flips true) — auto-billing stops there and sales handles invoicing manually.\n\n**Requires `account:read` scope on a `sk_int_*` integrator key.** No X-Firm-Id header — the endpoint is integrator-scoped, not firm-scoped.\n\n`billable` only includes firms on the `integrator-managed` plan (the integrator is the payer). `nonManaged` reports linked firms that pay their own plan. `perFirm` (returned as `firms`) breaks down both groups.",
        "operationId": "getIntegratorLicenseInfo",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "offset",
            "in": "query",
            "description": "Pagination offset for the per-firm list. Default 0.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Page size for the per-firm list. Max 100. Default 50.",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Plan + aggregate usage snapshot",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "integrator": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        },
                        "plan": {
                          "type": "string",
                          "example": "integrator"
                        },
                        "monthlyDocumentLimit": {
                          "type": "integer",
                          "nullable": true
                        }
                      }
                    },
                    "period": {
                      "type": "string",
                      "example": "2026-04",
                      "description": "Current billing period in YYYY-MM (SK timezone)."
                    },
                    "nextResetAt": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When counters reset — 1st of next month, SK midnight in UTC."
                    },
                    "billable": {
                      "type": "object",
                      "description": "Aggregate over firms on the `integrator-managed` plan (the integrator pays).",
                      "properties": {
                        "managedFirms": {
                          "type": "integer"
                        },
                        "outboundCount": {
                          "type": "integer"
                        },
                        "inboundApiCount": {
                          "type": "integer"
                        },
                        "outboundCharge": {
                          "type": "number",
                          "description": "Tier rates applied to the aggregate outboundCount."
                        },
                        "inboundApiCharge": {
                          "type": "number",
                          "description": "Tier rates applied to the aggregate inboundApiCount."
                        },
                        "totalCharge": {
                          "type": "number",
                          "description": "Sum of outboundCharge + inboundApiCharge, rounded to cents."
                        },
                        "currency": {
                          "type": "string",
                          "example": "EUR"
                        }
                      }
                    },
                    "nonManaged": {
                      "type": "object",
                      "description": "Linked firms that pay their own plan (not billed to the integrator).",
                      "properties": {
                        "firms": {
                          "type": "integer"
                        },
                        "outboundCount": {
                          "type": "integer"
                        },
                        "inboundApiCount": {
                          "type": "integer"
                        }
                      }
                    },
                    "exceedsAutoTier": {
                      "type": "boolean",
                      "description": "True when outboundCount or inboundApiCount exceeds contactThreshold. Auto-billing pauses; sales handles invoicing manually."
                    },
                    "contactThreshold": {
                      "type": "integer",
                      "example": 5000,
                      "description": "Threshold above which auto-billing stops and an individual contract is required."
                    },
                    "pricing": {
                      "type": "object",
                      "properties": {
                        "model": {
                          "type": "string",
                          "example": "tiered"
                        },
                        "currency": {
                          "type": "string",
                          "example": "EUR"
                        },
                        "outboundTiers": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "upTo": {
                                "type": "integer",
                                "nullable": true
                              },
                              "rate": {
                                "type": "number",
                                "nullable": true
                              },
                              "label": {
                                "type": "string"
                              },
                              "contactRequired": {
                                "type": "boolean"
                              }
                            }
                          }
                        },
                        "inboundApiTiers": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "upTo": {
                                "type": "integer",
                                "nullable": true
                              },
                              "rate": {
                                "type": "number",
                                "nullable": true
                              },
                              "label": {
                                "type": "string"
                              },
                              "contactRequired": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "firms": {
                      "type": "array",
                      "description": "Per-firm breakdown for the requested page (sorted by outboundCount desc).",
                      "items": {
                        "type": "object",
                        "properties": {
                          "firmId": {
                            "type": "string"
                          },
                          "name": {
                            "type": "string",
                            "nullable": true
                          },
                          "ico": {
                            "type": "string",
                            "nullable": true
                          },
                          "managed": {
                            "type": "boolean",
                            "description": "true → counts in `billable`; false → counts in `nonManaged`."
                          },
                          "outboundCount": {
                            "type": "integer"
                          },
                          "inboundApiCount": {
                            "type": "integer"
                          }
                        }
                      }
                    },
                    "pagination": {
                      "type": "object",
                      "properties": {
                        "limit": {
                          "type": "integer"
                        },
                        "offset": {
                          "type": "integer"
                        },
                        "total": {
                          "type": "integer"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Missing `account:read` scope or non-integrator key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Integrator not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/send": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Odoslat dokument cez Peppol",
        "description": "Odosle fakturu cez Peppol siet. Podporuje dva rezimy:\n\n**JSON mode** — poskytnete strukturovane data (`items`), UBL XML sa vygeneruje automaticky.\n\n**XML mode** — poskytnete hotove UBL XML (`xml`), odosle sa priamo.\n\nV oboch pripadoch je `receiverPeppolId` povinne.\n\n**Idempotency (always strict):** pass `Idempotency-Key: <any unique string>` (alias `X-Idempotency-Key`) to safely retry after network errors — the second call with the same body returns the cached 201 response. If the same key is currently being processed, returns 409 CONFLICT. If the body SHA-256 differs from the stored hash, returns 422 IDEMPOTENCY_KEY_MISMATCH (body is canonicalized — keys sorted recursively — before hashing). There is no legacy non-strict mode.\n\n**Cross-path dedup (XML mode):** if the same UBL XML SHA-256 was already accepted for this firm via dashboard mass-import, returns 200 with `{ data: { invoice_id, peppol_message_id, status, duplicate: true } }`.\n\n**Items[] max 999.** Unit codes use UN/ECE Rec 20 (C62, HUR, KGM, MTR, LTR, DAY, ...).\n\n**Attachments (BG-24):** up to 20 files, 10 MB per file, 15 MB total after base64 decode. MIME types enforced per BR-CL-22.\n\n**Required scope:** `documents:send`. Wildcard scopes (`*`, `full`, or empty `[]`) also satisfy this requirement.",
        "operationId": "sendDocument",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Client-supplied idempotency key (canonical RFC-draft header). If the same key was used recently with the same body, the prior response is replayed."
          },
          {
            "name": "X-Idempotency-Key",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "description": "Legacy alias for `Idempotency-Key`. Both headers are accepted."
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/SendDocumentJsonRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SendDocumentXmlRequest"
                  }
                ]
              },
              "examples": {
                "json_mode": {
                  "summary": "JSON mode",
                  "value": {
                    "receiverPeppolId": "0245:12345678",
                    "invoiceNumber": "FAK-2026-0001",
                    "issueDate": "2026-04-01",
                    "dueDate": "2026-04-15",
                    "currency": "EUR",
                    "iban": "SK1234567890123456789012",
                    "items": [
                      {
                        "description": "Konzultacne sluzby",
                        "quantity": 10,
                        "unit": "HUR",
                        "unitPrice": 50,
                        "vatRate": 23
                      }
                    ]
                  }
                },
                "json_mode_with_attachments": {
                  "summary": "JSON mode s prilohami (BG-24)",
                  "value": {
                    "receiverPeppolId": "0245:12345678",
                    "invoiceNumber": "FAK-2026-0001",
                    "items": [
                      {
                        "description": "Konzultacne sluzby",
                        "quantity": 10,
                        "unitPrice": 50,
                        "vatRate": 23
                      }
                    ],
                    "attachments": [
                      {
                        "fileName": "invoice-detail.pdf",
                        "mimeType": "application/pdf",
                        "content": "JVBERi0xLjQKJeLjz9MKMS...",
                        "description": "Rozpis odpracovanych hodin"
                      }
                    ]
                  }
                },
                "xml_mode": {
                  "summary": "XML mode",
                  "value": {
                    "receiverPeppolId": "0245:12345678",
                    "xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Dokument uspesne odoslany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendDocumentResponse"
                }
              }
            }
          },
          "202": {
            "description": "Dokument bol dorucenny cez Peppol, ale zapis lokalneho stavu zlyhal. Reconcile cron stav doplni; idempotent retry vrati identicku odpoved.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendDocumentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required or access denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency key in-flight — same X-Idempotency-Key is currently being processed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 25 MB JSON mode, > 5 MB XML mode)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error (Zod or Peppol schematron), or IDEMPOTENCY_KEY_MISMATCH when X-Idempotency-Mode: strict is set and the body hash differs from the originally stored request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolValidationErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "SEND_FAILED — Peppol AP dispatch failed (retryable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "IDEMPOTENCY_STORE_UNAVAILABLE — Redis idempotency layer unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/send/batch": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Send up to 50 documents in one call",
        "description": "Batch version of `/documents/send`. Each item is dispatched through the regular send pipeline and returns its own status + documentId (or per-item error). Partial success is expected — the HTTP status is 200 even if some items fail; inspect each result. Body size cap: 20 MB (use individual sends for larger batches).",
        "operationId": "sendDocumentBatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchSendRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch processed (partial-success semantics)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchSendResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 20 MB)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "items[] missing/empty or > 50 items",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/preflight": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Pred-odosielacia validacia",
        "description": "Skontroluje, ci je prijemca registrovany v Peppol sieti a podporuje dany typ dokumentu, este pred odoslanim.",
        "operationId": "preflightDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "receiverPeppolId"
                ],
                "properties": {
                  "receiverPeppolId": {
                    "type": "string",
                    "example": "0245:12345678",
                    "description": "Peppol ID prijemcu"
                  },
                  "documentTypeId": {
                    "type": "string",
                    "example": "urn:oasis:names:specification:ubl:schema:xsd:Invoice-2",
                    "description": "Typ dokumentu (volitelne, default: Invoice)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Vysledok preflight kontroly (tri-state booleans)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PreflightResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 6 MB)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Missing required fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "SMP lookup service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/validate": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Validovat dokument bez odoslania",
        "description": "Overi UBL XML alebo JSON data voci Peppol pravidlam bez skutocneho odoslania. Vhodne pre testovanie integracie. Body cap: 6 MB.",
        "operationId": "validateDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "$ref": "#/components/schemas/SendDocumentJsonRequest"
                  },
                  {
                    "$ref": "#/components/schemas/SendDocumentXmlRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validacia uspesna",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateDocumentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 6 MB)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Peppol validacia zlyhala",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolValidationErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "VALIDATION_SERVICE_UNAVAILABLE — ion-docval unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/inbox": {
      "get": {
        "tags": [
          "Inbox"
        ],
        "summary": "Zoznam prijatych dokumentov",
        "description": "Vrati strankovany zoznam dokumentov prijatych cez Peppol pre vasu firmu.",
        "operationId": "listInbox",
        "parameters": [
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Pocet preskocenych zaznamov"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Pocet vratenych zaznamov"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "RECEIVED",
                "ACKNOWLEDGED",
                "ACCEPTED",
                "REJECTED",
                "PAID",
                "VALIDATION_FAILED",
                "FAILED"
              ]
            },
            "description": "Filter podla stavu dokumentu"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Return only documents received after this ISO 8601 timestamp"
          },
          {
            "name": "peppolMessageId",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Filter by Peppol message ID (exact match)"
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from previous response's nextCursor. Stable pagination — preferred over offset for large result sets."
          }
        ],
        "responses": {
          "200": {
            "description": "Zoznam prijatych dokumentov (response root key je `documents`, kazdy item je normalizovany InvoiceResponse so supplier/customer/totals nested objektami podla `formatInvoice()` v `lib/api/format.ts`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required or access denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid status or since parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/inbox/{id}": {
      "get": {
        "tags": [
          "Inbox"
        ],
        "summary": "Detail prijateho dokumentu",
        "description": "Vrati kompletne data prijateho dokumentu vratane UBL XML payloadu (ak je dostupny).",
        "operationId": "getInboxDocument",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "Detail dokumentu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxDocumentDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Dokument nepatri vasej firme",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny alebo nie je inbound",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/inbox/{id}/acknowledge": {
      "post": {
        "tags": [
          "Inbox"
        ],
        "summary": "Potvrdit prijatie dokumentu",
        "description": "Oznaci dokument ako spracovany (`ACKNOWLEDGED`). Ak bol dokument uz potvrdeny, vrati chybu.",
        "operationId": "acknowledgeDocument",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "Dokument potvrdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AcknowledgeResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Dokument nepatri vasej firme alebo enterprise plan vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny alebo nie je inbound",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Dokument bol uz potvrdeny alebo zly source state",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/inbox/all": {
      "get": {
        "tags": [
          "Inbox"
        ],
        "summary": "Cross-firm bulk inbox",
        "description": "Returns received documents across all assigned firms. No X-Firm-Id header needed. Requires a JWT minted from an sk_int_* key.",
        "operationId": "getCrossFirmInbox",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Pagination offset"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            },
            "description": "Maximum number of documents to return (max 200)"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "RECEIVED",
                "ACKNOWLEDGED",
                "ACCEPTED",
                "REJECTED",
                "PAID",
                "VALIDATION_FAILED",
                "FAILED"
              ]
            },
            "description": "Filter by document status"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Return only documents received after this ISO 8601 timestamp"
          },
          {
            "name": "firm_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "description": "Filter by firm UUID"
          }
        ],
        "responses": {
          "200": {
            "description": "List of received documents across all firms",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CrossFirmInboxResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/outbox": {
      "get": {
        "tags": [
          "Outbox"
        ],
        "summary": "Zoznam odoslanych dokumentov",
        "description": "Vrati strankovany zoznam dokumentov odoslanych cez Peppol pre vasu firmu.",
        "operationId": "listOutbox",
        "parameters": [
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Pocet preskocenych zaznamov"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Pocet vratenych zaznamov"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "SENT",
                "DELIVERED",
                "FAILED",
                "REJECTED",
                "ACKNOWLEDGED",
                "SENDING",
                "SEND_FAILED",
                "VALIDATION_FAILED",
                "PAID",
                "DRAFT",
                "OVERDUE",
                "ACCEPTED"
              ]
            },
            "description": "Filter podla stavu dokumentu"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Return only documents created after this ISO 8601 timestamp"
          },
          {
            "name": "peppolMessageId",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            },
            "description": "Filter by Peppol message ID (exact match)"
          }
        ],
        "responses": {
          "200": {
            "description": "Zoznam odoslanych dokumentov (response root key je `documents`, kazdy item je normalizovany InvoiceResponse so supplier/customer/totals nested objektami podla `formatInvoice()` v `lib/api/format.ts`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OutboxListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required or access denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid status, since or peppolMessageId parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Detail dokumentu — invoice alebo non-billing Peppol doc",
        "description": "Vráti dokument podľa ID. Najprv hľadá medzi invoices (BIS Billing); ak ho tam nenájde, hľadá medzi peppol_documents (non-billing T01/T16/T19/T76 atď.). Tenant-isolated cez firmId — dokumenty inej firmy nikdy nevráti.\n\n**Required scope:** `documents:read`.",
        "operationId": "getDocument",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu (invoice alebo peppol_document)"
          }
        ],
        "responses": {
          "200": {
            "description": "Detail dokumentu — Invoice (BIS Billing) alebo PeppolDocument (non-billing) shape, podľa toho kam dokument patrí"
          },
          "401": {
            "description": "Neplatný alebo chýbajúci JWT",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plán vyžadovaný",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenájdený",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Documents"
        ],
        "summary": "Upraviť draft fakturu",
        "description": "Aktualizuje draft fakturu pred odoslaním. Pracuje len s `status=draft` invoice rows — non-draft a non-invoice dokumenty vrátia 422. Telo je strict (neznáme polia odmietne). Polia, ktoré nie sú v tele, ostanú nezmenené.\n\n**Required scope:** `documents:write`.",
        "operationId": "patchDocument",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID draft faktury"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "invoiceNumber": {
                    "type": "string",
                    "minLength": 1
                  },
                  "issueDate": {
                    "type": "string",
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "dueDate": {
                    "type": "string",
                    "nullable": true,
                    "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
                  },
                  "currency": {
                    "type": "string",
                    "minLength": 1
                  },
                  "note": {
                    "type": "string",
                    "nullable": true
                  },
                  "iban": {
                    "type": "string",
                    "nullable": true
                  },
                  "variableSymbol": {
                    "type": "string",
                    "nullable": true
                  },
                  "buyerReference": {
                    "type": "string",
                    "nullable": true
                  },
                  "receiverName": {
                    "type": "string",
                    "minLength": 1
                  },
                  "receiverIco": {
                    "type": "string",
                    "nullable": true
                  },
                  "receiverDic": {
                    "type": "string",
                    "nullable": true
                  },
                  "receiverIcDph": {
                    "type": "string",
                    "nullable": true
                  },
                  "receiverAddress": {
                    "type": "string",
                    "nullable": true
                  },
                  "receiverCountry": {
                    "type": "string",
                    "nullable": true
                  },
                  "receiverPeppolId": {
                    "type": "string",
                    "nullable": true
                  },
                  "items": {
                    "type": "array",
                    "minItems": 1,
                    "items": {
                      "type": "object",
                      "required": [
                        "description",
                        "quantity",
                        "unitPrice",
                        "vatRate"
                      ],
                      "properties": {
                        "description": {
                          "type": "string",
                          "minLength": 1
                        },
                        "quantity": {
                          "type": "number",
                          "exclusiveMinimum": 0
                        },
                        "unit": {
                          "type": "string",
                          "default": "C62",
                          "description": "UN/ECE Rec 20"
                        },
                        "unitPrice": {
                          "type": "number",
                          "minimum": 0
                        },
                        "vatRate": {
                          "type": "number",
                          "enum": [
                            0,
                            5,
                            10,
                            19,
                            23
                          ],
                          "description": "SK VAT rates from 2025"
                        },
                        "discount": {
                          "type": "number",
                          "minimum": 0,
                          "maximum": 100,
                          "default": 0
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated invoice",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error or non-draft / non-invoice document",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/status": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Stav dokumentu — uplny lifecycle",
        "description": "Vrati aktualny stav dokumentu vratane kompletnej historie stavov.",
        "operationId": "getDocumentStatus",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "Stav dokumentu",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentStatusResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/status/batch": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Hromadný status check (až 100 dokumentov)",
        "description": "Vráti stav až 100 dokumentov v jednom volaní. Výsledky sú v rovnakom poradí ako vstupné `ids`. Dokumenty, ktoré nepatria volajúcej firme alebo neexistujú, sú v odpovedi označené `{ \"error\": \"not_found\" }` — endpoint nikdy nevráti 404 pre celé volanie ani neprezradí existenciu dokumentov inej firmy.\n\n**Rate limit:** 300 req/min na API kľúč (až 30 000 stavov za minútu). Pre jednotlivé dokumenty použite `GET /documents/{id}/status` (limit 400 req/min).\n\n**Telo cap:** 64 KB.\n\n**Required scope:** `documents:read`.",
        "operationId": "batchDocumentStatus",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "ids"
                ],
                "properties": {
                  "ids": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 100,
                    "items": {
                      "type": "string",
                      "minLength": 1
                    },
                    "description": "Pole 1-100 document IDs. Duplicity sú deduplikované pred query, ale poradie volajúceho je zachované v results."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Per-item statuses v poradí volajúcich `ids`",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "total": {
                      "type": "integer",
                      "example": 42
                    },
                    "found": {
                      "type": "integer",
                      "example": 38,
                      "description": "Count of items in results that are NOT marked {error: not_found}."
                    },
                    "notFound": {
                      "type": "integer",
                      "example": 4
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "oneOf": [
                          {
                            "type": "object",
                            "required": [
                              "id",
                              "status",
                              "statusHistory",
                              "createdAt",
                              "updatedAt"
                            ],
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "documentType": {
                                "type": "string",
                                "nullable": true
                              },
                              "direction": {
                                "type": "string",
                                "enum": ["inbound", "outbound"],
                                "description": "`inbound` = my sme prijímateľ. `outbound` = my sme odosielateľ. Sémantika `deliveredAt`/`acknowledgedAt` závisí od smeru — pozri DocumentStatusResponse popis."
                              },
                              "senderPeppolId": {
                                "type": "string",
                                "nullable": true
                              },
                              "receiverPeppolId": {
                                "type": "string",
                                "nullable": true
                              },
                              "statusHistory": {
                                "type": "array",
                                "items": {
                                  "type": "object"
                                }
                              },
                              "validationResult": {
                                "type": "object",
                                "nullable": true
                              },
                              "deliveredAt": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                              },
                              "acknowledgedAt": {
                                "type": "string",
                                "format": "date-time",
                                "nullable": true
                              },
                              "invoiceResponseStatus": {
                                "type": "string",
                                "nullable": true
                              },
                              "peppolMessageId": {
                                "type": "string",
                                "nullable": true,
                                "description": "Alias k `as4MessageId` (rovnaká hodnota)."
                              },
                              "as4MessageId": {
                                "type": "string",
                                "nullable": true,
                                "description": "Alias k `peppolMessageId` (rovnaká hodnota)."
                              },
                              "createdAt": {
                                "type": "string",
                                "format": "date-time"
                              },
                              "updatedAt": {
                                "type": "string",
                                "format": "date-time"
                              }
                            }
                          },
                          {
                            "type": "object",
                            "required": [
                              "id",
                              "error"
                            ],
                            "properties": {
                              "id": {
                                "type": "string"
                              },
                              "error": {
                                "type": "string",
                                "enum": [
                                  "not_found"
                                ]
                              }
                            }
                          }
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid request — empty ids[], > 100, non-string elements",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Body exceeds 64 KB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/events": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Document timeline — AS4/Peppol/lifecycle events",
        "description": "Paginated, reverse-chronological timeline for a single document: creation, validation, AS4 send/receipt, MLR, invoice response, FS SR reporting, user actions. Synthesized from invoice fields when `document_events` has no rows yet, real DB rows once events are emitted.",
        "operationId": "listDocumentEvents",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Document ID"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from previous response"
          }
        ],
        "responses": {
          "200": {
            "description": "Event list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEventsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/mark": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Report downstream state (delivered/processed/failed/read)",
        "description": "Granular state-transition endpoint for integrators running their own processing pipeline. Report when the external channel confirmed delivery, the ERP consumed the document, the user opened it, or delivery terminally failed. Keeps status/events/reporting accurate.",
        "operationId": "markDocumentState",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Document ID"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MarkRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "State updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarkResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid state value",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/pdf": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Stiahnut PDF dokumentu",
        "description": "Vrati PDF verziu faktury ako binarny subor.",
        "operationId": "getDocumentPdf",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "PDF subor",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny alebo PDF nie je dostupne",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/ubl": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Stiahnut UBL XML dokumentu",
        "description": "Vrati UBL XML reprezentaciu dokumentu.",
        "operationId": "getDocumentUbl",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "UBL XML subor",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny alebo UBL nie je dostupne",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/evidence": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Dokazy o doruceni dokumentu",
        "description": "Vrati AS4 receipt, Message Level Response (MLR) a invoice response pre odoslany dokument.",
        "operationId": "getDocumentEvidence",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "Dokazy o doruceni",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DocumentEvidenceResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/envelope": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Download signed AS4 envelope (10-year WORM archive)",
        "description": "Streams the raw multipart AS4 envelope for this document from the 10-year WORM archive (S3 Object Lock COMPLIANCE mode) exactly as it was transmitted on the Peppol network — signed, timestamped, tamper-evident. Response headers include `X-Envelope-Archived-At` (ISO 8601) and `X-Envelope-Direction` (inbound|outbound).\n\nArchive cron runs on a short interval, so brand-new documents may briefly 404 until archived.\n\n**Requires `api-enterprise` plan.**",
        "operationId": "getDocumentEnvelope",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Document ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Signed AS4 envelope",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string",
                  "example": "attachment; filename=\"<id>.as4\""
                }
              },
              "X-Envelope-Archived-At": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "When the envelope was written to the WORM archive"
              },
              "X-Envelope-Direction": {
                "schema": {
                  "type": "string",
                  "enum": [
                    "inbound",
                    "outbound"
                  ]
                }
              }
            },
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              },
              "multipart/related": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Document not found OR envelope not yet archived",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/responses": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "Zoznam invoice responses dokumentu",
        "description": "Vrati zoznam invoice responses (AP/RE/UQ) pre odoslany dokument.",
        "operationId": "listDocumentResponses",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "responses": {
          "200": {
            "description": "Zoznam invoice responses",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceResponsesListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/{id}/respond": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Odoslat invoice response",
        "description": "Odosle invoice response (UBL ApplicationResponse) pre prijaty dokument. Podporuje 7 stavov: AB, IP, UQ, CA, RE, AP, PD.\n\nReturns HTTP 200 when AS4 dispatch succeeded, 202 when the response was persisted but dispatch failed (will retry async — inspect `dispatchError`).",
        "operationId": "respondToDocument",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID dokumentu"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/InvoiceRespondRequest"
              },
              "example": {
                "status": "AP",
                "note": "Faktura akceptovana"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Invoice response dispatched successfully via AS4",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceRespondResponse"
                }
              }
            }
          },
          "202": {
            "description": "Response persisted; AS4 dispatch failed and will be retried (inspect `dispatchError`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InvoiceRespondResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required or access denied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Dokument nenajdeny alebo nie je inbound",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid status value, invoice response already submitted, or missing Peppol identifiers",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/parse": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Parse raw UBL into normalized JSON",
        "description": "Parse-as-a-service: accept raw UBL 2.1 Invoice or CreditNote XML and return the normalized JSON structure the send endpoint would accept. Useful when ingesting invoices over non-Peppol channels (email, SFTP) or migrating from another AP.\n\nAccepts either `Content-Type: application/xml` (body = XML) or `application/json` with `{ xml: \"...\" }`. Body cap: 10 MB.\n\nThe supplier `EndpointID` in the submitted XML must match the authenticated firm's Peppol ID. Forwarding cross-tenant XML is rejected with 422 (`VALIDATION_ERROR`). Documents that omit the supplier `EndpointID` entirely (legacy/OCR imports) are accepted. The same guard is applied to `/documents/convert`, `/documents/validate` (UBL mode) and `/documents/preflight` (when raw XML is supplied).",
        "operationId": "parseDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/xml": {
              "schema": {
                "type": "string",
                "format": "xml"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "xml"
                ],
                "properties": {
                  "xml": {
                    "type": "string",
                    "description": "UBL Invoice or CreditNote XML"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Parsed invoice payload",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParseResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 10 MB)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation or parse error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/documents/convert": {
      "post": {
        "tags": [
          "Documents"
        ],
        "summary": "Konverzia formatov dokumentu",
        "description": "Konvertuje medzi JSON a UBL XML formatmi. Smer uvedte cez `input_format` (`json`|`ubl`) a `output_format` (`ubl`|`json`). Podporovane pary: json→ubl, ubl→json.",
        "operationId": "convertDocument",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConvertDocumentRequest"
              },
              "examples": {
                "jsonToUbl": {
                  "summary": "JSON → UBL",
                  "value": {
                    "input_format": "json",
                    "output_format": "ubl",
                    "document": {
                      "invoiceNumber": "FAK-001",
                      "items": []
                    }
                  }
                },
                "ublToJson": {
                  "summary": "UBL → JSON",
                  "value": {
                    "input_format": "ubl",
                    "output_format": "json",
                    "document": "<?xml version=\"1.0\"?>..."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Konverzia uspesna. Pre `output_format=ubl` je `document` string, pre `output_format=json` je objekt.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConvertDocumentResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 6 MB)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Konverzia zlyhala alebo nepodporovana kombinacia input_format/output_format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Backend conversion service unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/peppol-documents": {
      "get": {
        "tags": [
          "Documents"
        ],
        "summary": "List non-billing Peppol documents",
        "description": "List endpoint for non-billing Peppol doctypes (Order, OrderResponse, DespatchAdvice, Catalogue, CatalogueResponse, OrderAgreement, AdvancedOrdering family, PunchOut). Lives in its own `peppol_documents` table to avoid changing the `/documents/inbox`/`/documents/outbox` Invoice-shaped contract.\n\n**Requires `documents:read` scope and `api-enterprise` plan.**",
        "operationId": "listPeppolDocuments",
        "parameters": [
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            }
          },
          {
            "name": "doctypeKey",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Specific non-billing doctype key (see registry)"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "peppolMessageId",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Non-billing Peppol documents",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "documents": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "document_id": {
                            "type": "string"
                          },
                          "doctype_key": {
                            "type": "string"
                          },
                          "direction": {
                            "type": "string",
                            "enum": [
                              "inbound",
                              "outbound"
                            ]
                          },
                          "status": {
                            "type": "string"
                          },
                          "sender_peppol_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "receiver_peppol_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "sender_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "receiver_name": {
                            "type": "string",
                            "nullable": true
                          },
                          "document_type_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "process_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "peppol_message_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "updated_at": {
                            "type": "string",
                            "format": "date-time"
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not allow this endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/extract": {
      "post": {
        "tags": [
          "Extraction"
        ],
        "summary": "Extrahovat fakturu z PDF alebo obrazka",
        "description": "Nahra PDF alebo obrazok faktury, extrahuje data pomocou Gemini Vision a vrati strukturovane data spolu s vygenerovanym UBL XML.\n\n**Vyzaduje plan `api-enterprise`.** Rate limit: 10 req/min per firm.\n\nPovolene typy: `application/pdf`, `image/jpeg`, `image/png`, `image/webp`. Max. velkost: 20 MB.",
        "operationId": "extractDocument",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "PDF alebo obrazok faktury (max 20 MB)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Extrakcia uspesna",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractResponse"
                }
              }
            }
          },
          "400": {
            "description": "Chybajuci subor, nepodporovany typ alebo prilis velky subor",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimpleErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan api-enterprise je vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SimpleErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Extrakcia alebo UBL generovanie zlyhalo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExtractErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (10/min/firm)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Gemini API key not configured (SERVICE_UNAVAILABLE)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/extract/batch": {
      "post": {
        "tags": [
          "Extraction"
        ],
        "summary": "Batch OCR extraction",
        "description": "Extract invoice data from multiple PDF/image files. Max 50 files, 20 MB per file, 200 MB total request body. Rate limit: 3 req/min per firm.\n\n**Requires `api-enterprise` plan.**",
        "operationId": "extractBatch",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "files"
                ],
                "properties": {
                  "files": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "format": "binary"
                    },
                    "maxItems": 50,
                    "description": "PDF or image files"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch extraction results (per-file success or error)"
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Total body exceeds 200 MB or individual file > 20 MB",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (3/min/firm)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Gemini API not configured",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Vytvorit webhook",
        "description": "Zaregistruje novu webhook URL pre vybrane udalosti. URL musi byt HTTPS a nesmie byt localhost ani privatna siet (SSRF ochrana). Max 10 webhookov per firma.\n\n**Vyzaduje plan `api-enterprise`.**\n\nPodporovane udalosti (7): `document.created`, `document.sent`, `document.received`, `document.validated`, `document.delivered`, `document.rejected`, `document.response_received`.",
        "operationId": "createWebhook",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              },
              "example": {
                "url": "https://vas-system.sk/webhooks/epostak",
                "events": [
                  "document.received",
                  "document.sent"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook vytvoreny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDetailResponse"
                }
              }
            }
          },
          "400": {
            "description": "Neplatna URL alebo udalosti",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan api-enterprise je vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Zoznam webhookov",
        "description": "Vrati vsetky webhook odoberania pre vasu firmu.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "listWebhooks",
        "responses": {
          "200": {
            "description": "Zoznam webhookov",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan api-enterprise je vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Detail webhooku",
        "description": "Vrati detaily webhooku vratane poslednych 20 doruceni.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "getWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID webhooku"
          }
        ],
        "responses": {
          "200": {
            "description": "Detail webhooku",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookWithDeliveriesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook nenajdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Upravit webhook",
        "description": "Aktualizuje URL, zoznam udalosti alebo aktivny stav webhooku. Vsetky polia su volitelne.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "updateWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID webhooku"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Webhook aktualizovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookResponse"
                }
              }
            }
          },
          "400": {
            "description": "Neplatna URL alebo udalosti",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty alebo plan api-enterprise vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook nenajdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Zmazat webhook",
        "description": "Natrvalo zmaze webhook.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID webhooku"
          }
        ],
        "responses": {
          "204": {
            "description": "Webhook zmazany (prazdne telo odpovede)"
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty alebo plan api-enterprise vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook nenajdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a synthetic test delivery",
        "description": "Dispatches a test payload to the webhook URL with a fresh HMAC signature. Returns HTTP 200 with a result envelope regardless of whether the delivery succeeded — inspect `success` and `statusCode`. A `webhook_deliveries` row is written so the attempt appears in `/webhooks/{id}/deliveries`.\n\n**Requires `api-enterprise` plan.**",
        "operationId": "testWebhook",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event": {
                    "type": "string",
                    "enum": [
                      "document.created",
                      "document.sent",
                      "document.received",
                      "document.validated",
                      "document.delivered",
                      "document.rejected",
                      "document.response_received"
                    ],
                    "description": "Event to simulate (default: document.created)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Test dispatched (check `success` for delivery outcome)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid event name or URL fails SSRF re-check",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Paginated delivery history",
        "description": "Returns the full delivery log for a webhook (test deliveries included). Supports status + event filters.\n\n**`responseBody` is omitted by default** because a legitimate webhook receiver may echo sensitive content (PII, tokens) in its 4xx/5xx body. Pass `?includeResponseBody=true` (or `?include=responseBody`) to opt in. The first 200 chars are stored at delivery time — older deliveries written before this cap shipped may be longer.\n\n**Requires `api-enterprise` plan.**",
        "operationId": "listWebhookDeliveries",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "PENDING",
                "SUCCESS",
                "FAILED",
                "RETRYING"
              ]
            },
            "description": "Filter by delivery status (UPPERCASE enum)"
          },
          {
            "name": "event",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Filter by event name"
          },
          {
            "name": "includeResponseBody",
            "in": "query",
            "schema": {
              "type": "boolean",
              "default": false
            },
            "description": "Set to `true` to include the (truncated, 200-char) `responseBody` field on each delivery. Default `false` to avoid leaking content from receivers that echo sensitive data."
          },
          {
            "name": "include",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "responseBody"
              ]
            },
            "description": "Alternative spelling: `?include=responseBody` is equivalent to `?includeResponseBody=true`."
          }
        ],
        "responses": {
          "200": {
            "description": "Delivery list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliveriesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/rotate-secret": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate HMAC signing secret",
        "description": "Generates a new 64-char hex secret and immediately discards the old one. In-flight deliveries signed with the old secret will fail signature verification at the receiver. Returns the new secret ONCE.",
        "operationId": "rotateWebhookSecret",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Webhook ID"
          }
        ],
        "responses": {
          "200": {
            "description": "Secret rotated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookRotateSecretResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Access denied or enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Webhook not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-queue": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Nacitat neuznane udalosti",
        "description": "Vrati zoznam neuznanych (nepotvrdenych) udalosti z webhook frontu. Alternativa k push webhookom pre polling pristup.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "getWebhookQueue",
        "responses": {
          "200": {
            "description": "Zoznam cakajucich udalosti",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookQueueResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan api-enterprise je vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-queue/{eventId}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Potvrdit jednu udalost",
        "description": "Potvrdi (acknowledge) jednu udalost a odstrani ju z frontu.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "acknowledgeWebhookEvent",
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID udalosti"
          }
        ],
        "responses": {
          "200": {
            "description": "Udalost potvrdena",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "acknowledged": {
                      "type": "boolean",
                      "example": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan api-enterprise je vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Udalost nenajdena",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-queue/batch-ack": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Hromadne potvrdenie udalosti",
        "description": "Potvrdi (acknowledge) viacero udalosti naraz.\n\n**Vyzaduje plan `api-enterprise`.**",
        "operationId": "batchAcknowledgeWebhookEvents",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "eventIds"
                ],
                "properties": {
                  "eventIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "example": [
                      "evt_1",
                      "evt_2",
                      "evt_3"
                    ],
                    "description": "Zoznam ID udalosti na potvrdenie"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Udalosti potvrdene",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "acknowledged": {
                      "type": "integer",
                      "example": 3,
                      "description": "Pocet potvrdenych udalosti"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Chybajuci alebo prazdny zoznam eventIds",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan api-enterprise je vyzadovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-queue/all": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Cross-firm webhook queue poll",
        "description": "Poll pending webhook events across all assigned firms. No X-Firm-Id header needed. Requires a JWT minted from an sk_int_* key.",
        "operationId": "getCrossFirmWebhookQueue",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            },
            "description": "Maximum number of events to return (max 500)"
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            },
            "description": "Return only events created after this ISO 8601 timestamp"
          }
        ],
        "responses": {
          "200": {
            "description": "Pending webhook events across all firms",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CrossFirmWebhookQueueResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-queue/all/batch-ack": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Cross-firm batch acknowledge webhook events",
        "description": "Acknowledge multiple webhook events across all firms in a single request. Maximum 1000 event IDs.",
        "operationId": "batchAckCrossFirmWebhookEvents",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchAckWebhookRequest"
              },
              "example": {
                "event_ids": [
                  "uuid-1",
                  "uuid-2"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Number of events acknowledged",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchAckWebhookResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/peppol/participants/{scheme}/{identifier}": {
      "get": {
        "tags": [
          "Peppol"
        ],
        "summary": "SMP lookup ucastnika",
        "description": "Vyhlada Peppol ucastnika v SMP (Service Metadata Publisher) podla scheme a identifikatora.",
        "operationId": "smpLookup",
        "parameters": [
          {
            "name": "scheme",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "0245"
            },
            "description": "ICD scheme kod (napr. 0245 pre slovenske ICO)"
          },
          {
            "name": "identifier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "12345678"
            },
            "description": "Identifikator v danej scheme"
          }
        ],
        "responses": {
          "200": {
            "description": "Ucastnik najdeny",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SmpLookupResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Ucastnik nie je registrovany v Peppol sieti",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/peppol/participants/batch": {
      "post": {
        "tags": [
          "Peppol"
        ],
        "summary": "Bulk participant existence + capability lookup",
        "description": "Pre-check a list of recipients before fan-out send. Max 100 per batch. Individual results are cached so repeat queries are cheap.",
        "operationId": "peppolParticipantsBatch",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ParticipantsBatchRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParticipantsBatchResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "participants[] missing/empty or > 100",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/peppol/capabilities": {
      "post": {
        "tags": [
          "Peppol"
        ],
        "summary": "Capability probe — does receiver accept this doctype?",
        "description": "Storecove-style `discovery/receives`: given a participant + optional candidate document type + process id, answer whether the receiver accepts it. More than mere existence, less than a send. Callers fan out to the correct doctype (Invoice vs CreditNote vs SelfBilling vs InvoiceResponse) before building UBL.\n\nResponse `matchedDocumentType` is tri-state: the matched doctype string, `null` when no documentType filter was supplied (and `accepts` defaults to `true`), or `null` with `accepts:false` when the filter didn't match.",
        "operationId": "peppolCapabilities",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CapabilitiesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Lookup result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapabilitiesResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Participant not registered in Peppol network",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CapabilitiesResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid scheme/identifier format",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/peppol/directory/search": {
      "get": {
        "tags": [
          "Peppol"
        ],
        "summary": "Prehladavat Peppol directory",
        "description": "Prehladava Peppol Business Card directory podla nazvu firmy, krajiny a inych kriterii.",
        "operationId": "peppolDirectorySearch",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "Moja firma"
            },
            "description": "Vyhladavaci vyraz (nazov firmy, ICO)"
          },
          {
            "name": "country",
            "in": "query",
            "schema": {
              "type": "string",
              "example": "SK"
            },
            "description": "Filtrovanie podla kodu krajiny (ISO 3166-1 alpha-2)"
          },
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "default": 1
            },
            "description": "Cislo strany"
          },
          {
            "name": "page_size",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Pocet vysledkov na stranu"
          }
        ],
        "responses": {
          "200": {
            "description": "Vysledky vyhladavania",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolDirectorySearchResponse"
                }
              }
            }
          },
          "400": {
            "description": "Neplatne parametre",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/company/lookup/{ico}": {
      "get": {
        "tags": [
          "Peppol"
        ],
        "summary": "Vyhladanie firmy podla ICO",
        "description": "Vyhlada verejne informacie o firme podla ICO (slovensky obchodny register).",
        "operationId": "companyLookup",
        "parameters": [
          {
            "name": "ico",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "example": "12345678"
            },
            "description": "ICO firmy (8 cislic)"
          }
        ],
        "responses": {
          "200": {
            "description": "Informacie o firme",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompanyLookupResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Firma s danym ICO nenajdena",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/firms": {
      "get": {
        "tags": [
          "Firms"
        ],
        "summary": "Zoznam pristupnych firiem",
        "description": "Vrati zoznam firiem, ku ktorym ma API kluc pristup. Relevantne pre multi-tenant API kluce.",
        "operationId": "listFirms",
        "responses": {
          "200": {
            "description": "Zoznam firiem",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmsListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/firms/{id}": {
      "get": {
        "tags": [
          "Firms"
        ],
        "summary": "Detail firmy",
        "description": "Vrati detailne informacie o konkretnej firme vratane Peppol statusu.",
        "operationId": "getFirm",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID firmy"
          }
        ],
        "responses": {
          "200": {
            "description": "Detail firmy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FirmDetailResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Firma nenajdena",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/firms/{id}/documents": {
      "get": {
        "tags": [
          "Firms"
        ],
        "summary": "Dokumenty firmy",
        "description": "Vrati strankovany zoznam dokumentov (odoslanych aj prijatych) pre danu firmu.",
        "operationId": "listFirmDocuments",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID firmy"
          },
          {
            "name": "offset",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            },
            "description": "Pocet preskocenych zaznamov"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            },
            "description": "Pocet vratenych zaznamov"
          },
          {
            "name": "direction",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "inbound",
                "outbound"
              ]
            },
            "description": "Filter podla smeru"
          }
        ],
        "responses": {
          "200": {
            "description": "Zoznam dokumentov firmy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InboxListResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Firma nenajdena",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/firms/{id}/peppol-identifiers": {
      "post": {
        "tags": [
          "Firms"
        ],
        "summary": "Registrovat Peppol identifikator",
        "description": "Zaregistruje novy Peppol identifikator (scheme:identifier) pre firmu v SMP.",
        "operationId": "registerPeppolIdentifier",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "ID firmy"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scheme",
                  "identifier"
                ],
                "properties": {
                  "scheme": {
                    "type": "string",
                    "example": "0245",
                    "description": "ICD scheme kod"
                  },
                  "identifier": {
                    "type": "string",
                    "example": "12345678",
                    "description": "Identifikator v danej scheme"
                  }
                }
              },
              "example": {
                "scheme": "0245",
                "identifier": "12345678"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Peppol identifikator zaregistrovany",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PeppolIdentifierResponse"
                }
              }
            }
          },
          "400": {
            "description": "Neplatny scheme alebo identifikator",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Pristup odmietnuty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Identifikator uz existuje",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/firms/assign": {
      "post": {
        "tags": [
          "Firms"
        ],
        "summary": "Assign firm by ICO",
        "description": "Assign a firm to the enterprise account by its ICO. Requires a JWT minted from an sk_int_* key. No X-Firm-Id header needed.",
        "operationId": "assignFirmByIco",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignFirmRequest"
              },
              "example": {
                "ico": "12345678"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Firm assigned successfully (plan automatically promoted to `integrator-managed`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AssignFirmResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing integrator token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Missing `firms:manage` scope, or firm already on a paid plan (must use OAuth consent flow)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "404": {
            "description": "Firm not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Already assigned and active",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "UNPROCESSABLE_ENTITY — ICO must be exactly 8 digits",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/firms/assign/batch": {
      "post": {
        "tags": [
          "Firms"
        ],
        "summary": "Batch assign firms by ICO list",
        "description": "Assign multiple firms at once. Maximum 50 ICOs per request.",
        "operationId": "batchAssignFirms",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchAssignFirmsRequest"
              },
              "example": {
                "icos": [
                  "12345678",
                  "87654321"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch assign results (per-ICO success or error)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchAssignFirmsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Unauthorized",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Validation error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/reporting/statistics": {
      "get": {
        "tags": [
          "Reporting"
        ],
        "summary": "Statistiky dokumentov",
        "description": "Vrati agregovane statistiky odoslanych a prijatych dokumentov za zvolene obdobie.",
        "operationId": "getStatistics",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-01-01"
            },
            "description": "Zaciatok obdobia (YYYY-MM-DD)"
          },
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2026-03-31"
            },
            "description": "Koniec obdobia (YYYY-MM-DD)"
          }
        ],
        "responses": {
          "200": {
            "description": "Statistiky dokumentov",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/StatisticsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Neplatny alebo chybajuci API kluc",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/reporting/submissions": {
      "get": {
        "tags": [
          "Reporting"
        ],
        "summary": "List EUSR/TSR submissions to FS SR",
        "description": "Paginated read-only history of regulatory reporting submissions sent by ePošťák as Peppol AP operator to the Slovak FS SR. Mirrors the audit data Asseco APEX exposes. Internal fields (checksum, raw error message, receiver ID) are intentionally omitted; clients receive a `has_error` boolean instead.\n\nThis is a global operator-level history (not per-firm), reflecting that EUSR/TSR are AP-operator obligations.",
        "operationId": "listReportingSubmissions",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          },
          {
            "name": "report_type",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "EUSR",
                "TSR"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Paginated list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReportingSubmissionsResponse"
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Enterprise plan required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ForbiddenErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "Invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/audit": {
      "get": {
        "tags": [
          "Account"
        ],
        "summary": "Audit event feed (Wave 3.4)",
        "description": "Per-firm security/auth audit log. Tenant-isolated by firmId from JWT (sk_live_*) or X-Firm-Id header (sk_int_*). Cursor pagination over `(occurred_at DESC, id DESC)`.\n\n**Requires `audit:read` scope and `api-enterprise` plan.**",
        "operationId": "listAuditEvents",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "event",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact event name match (e.g. `jwt.issued`, `webhook.created`)"
          },
          {
            "name": "actor_type",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "user",
                "apiKey",
                "integratorKey",
                "system"
              ]
            }
          },
          {
            "name": "since",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "until",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Opaque cursor from previous response's `next_cursor`"
          }
        ],
        "responses": {
          "200": {
            "description": "Audit events",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "items",
                    "has_more"
                  ],
                  "properties": {
                    "items": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "string",
                            "format": "uuid"
                          },
                          "occurred_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "actor_type": {
                            "type": "string"
                          },
                          "actor_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "event": {
                            "type": "string"
                          },
                          "target_type": {
                            "type": "string",
                            "nullable": true
                          },
                          "target_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "ip": {
                            "type": "string",
                            "nullable": true
                          },
                          "user_agent": {
                            "type": "string",
                            "nullable": true
                          },
                          "metadata": {
                            "type": "object",
                            "additionalProperties": true,
                            "nullable": true
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    },
                    "has_more": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Invalid or missing token",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "403": {
            "description": "Plan does not allow this endpoint",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/validate": {
      "post": {
        "tags": [
          "Validation"
        ],
        "summary": "Public UBL validator (no auth)",
        "description": "**Public endpoint — NOT under `/api/v1/`.** Full path: `https://epostak.sk/api/validate`.\n\nValidator-as-a-service. Anyone can POST UBL XML (or `{ xml: \"...\" }` JSON) and get back the full 3-layer report our own send pipeline runs: UBL 2.1 XSD, EN 16931, Peppol BIS 3.0 schematrons. Rate-limited per-IP to 20/min. Body cap: 10 MB. Designed as a dry-run hook for integrators pre-signup.\n\nServer: `https://epostak.sk` (override the `/api/v1` server shown elsewhere).",
        "operationId": "validatePublic",
        "servers": [
          {
            "url": "https://epostak.sk/api",
            "description": "Public base (no /v1 prefix)"
          }
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/xml": {
              "schema": {
                "type": "string",
                "format": "xml"
              }
            },
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "xml"
                ],
                "properties": {
                  "xml": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Validation report (3-layer)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidateResponse"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "413": {
            "description": "Payload too large (> 10 MB)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "422": {
            "description": "body.xml is required or empty body",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded (20/min/IP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "ion-docval unreachable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "OAuth 2.0 client_credentials JWT access token (15 min TTL).\n\n**Obtain a token:** `POST /api/v1/auth/token` with the `client_id` shown with the key plus `client_secret` (the full `sk_live_*` or `sk_int_*` API key). Response contains `access_token` (JWT) and `refresh_token`.\n\n**Use the JWT:** `Authorization: Bearer eyJhbGciOiJSUzI1NiIs...`\n\n**Two key types:**\n- JWT minted from `sk_live_*` — direct firm access, no X-Firm-Id needed.\n- JWT minted from `sk_int_*` — requires `X-Firm-Id: <UUID>` header to target a specific assigned firm. For cross-firm endpoints (`/documents/inbox/all`, `/webhook-queue/all`, `/firms/assign`) X-Firm-Id is not required.\n\n**Direct API key bearer (Bearer sk_live_… or Bearer sk_int_…) is no longer accepted.**\n\nRate limit: 200 requests per minute per key."
      },
      "integratorBearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "JWT access token minted from a `sk_int_*` integrator API key via `POST /api/v1/auth/token`. Cross-firm endpoints require no X-Firm-Id header."
      }
    },
    "schemas": {
      "LineItem": {
        "type": "object",
        "required": [
          "description",
          "quantity",
          "unitPrice",
          "vatRate"
        ],
        "properties": {
          "description": {
            "type": "string",
            "example": "Konzultacne sluzby"
          },
          "quantity": {
            "type": "number",
            "example": 10
          },
          "unit": {
            "type": "string",
            "example": "HUR",
            "description": "Jednotka (UN/CEFACT kod). Napr. HUR=hodiny, C62=kusy, KGM=kg."
          },
          "unitPrice": {
            "type": "number",
            "example": 50
          },
          "vatRate": {
            "type": "number",
            "example": 23,
            "description": "DPH sadzba v percentach"
          },
          "discount": {
            "type": "number",
            "example": 10,
            "description": "Zlava v percentach (volitelne)"
          }
        }
      },
      "SendDocumentJsonRequest": {
        "type": "object",
        "required": [
          "receiverPeppolId",
          "items"
        ],
        "description": "JSON mode — UBL XML sa generuje automaticky",
        "properties": {
          "receiverPeppolId": {
            "type": "string",
            "example": "0245:12345678",
            "description": "Peppol ID prijemcu"
          },
          "invoiceNumber": {
            "type": "string",
            "example": "FAK-2026-0001",
            "description": "Cislo faktury (ak nie je zadane, generuje sa automaticky)"
          },
          "issueDate": {
            "type": "string",
            "format": "date",
            "example": "2026-04-01"
          },
          "dueDate": {
            "type": "string",
            "format": "date",
            "example": "2026-04-15"
          },
          "currency": {
            "type": "string",
            "example": "EUR",
            "default": "EUR"
          },
          "note": {
            "type": "string",
            "example": "Dakujeme za objednavku."
          },
          "iban": {
            "type": "string",
            "example": "SK1234567890123456789012"
          },
          "paymentMethod": {
            "type": "string",
            "example": "bank_transfer"
          },
          "variableSymbol": {
            "type": "string",
            "example": "20260001"
          },
          "buyerReference": {
            "type": "string",
            "example": "PO-2026-99"
          },
          "receiverName": {
            "type": "string",
            "example": "Zakaznik s.r.o."
          },
          "receiverIco": {
            "type": "string",
            "example": "12345678"
          },
          "receiverDic": {
            "type": "string",
            "example": "2020123456"
          },
          "receiverIcDph": {
            "type": "string",
            "example": "SK2020123456"
          },
          "receiverAddress": {
            "type": "string",
            "example": "Hlavna 1, Bratislava, 81101"
          },
          "receiverCountry": {
            "type": "string",
            "example": "SK",
            "default": "SK"
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "items": {
              "$ref": "#/components/schemas/LineItem"
            }
          },
          "attachments": {
            "type": "array",
            "maxItems": 20,
            "description": "Prilohy faktury (BG-24). Vlozia sa do UBL XML ako base64 cez AdditionalDocumentReference / EmbeddedDocumentBinaryObject. Max 20 suborov, 10 MB na subor, 15 MB celkovo.",
            "items": {
              "$ref": "#/components/schemas/DocumentAttachment"
            }
          }
        }
      },
      "DocumentAttachment": {
        "type": "object",
        "required": [
          "fileName",
          "mimeType",
          "content"
        ],
        "description": "Priloha faktury zakodovana ako base64. MIME typ sa overuje magic-byte kontrolou.",
        "properties": {
          "fileName": {
            "type": "string",
            "maxLength": 255,
            "example": "invoice-detail.pdf",
            "description": "Nazov suboru"
          },
          "mimeType": {
            "type": "string",
            "enum": [
              "application/pdf",
              "image/png",
              "image/jpeg",
              "text/csv",
              "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
              "application/vnd.oasis.opendocument.spreadsheet"
            ],
            "example": "application/pdf",
            "description": "Povolene MIME typy podla BR-CL-22"
          },
          "content": {
            "type": "string",
            "format": "byte",
            "description": "Base64-encoded obsah suboru (bez data: prefixu). Max 10 MB po dekodovani.",
            "example": "JVBERi0xLjQKJeLjz9MKMS..."
          },
          "description": {
            "type": "string",
            "maxLength": 100,
            "example": "Rozpis odpracovanych hodin",
            "description": "Kratky popis prilohy (volitelne)"
          }
        }
      },
      "SendDocumentXmlRequest": {
        "type": "object",
        "required": [
          "receiverPeppolId",
          "xml"
        ],
        "description": "XML mode — poskytnute UBL XML sa odosle priamo",
        "properties": {
          "receiverPeppolId": {
            "type": "string",
            "example": "0245:12345678"
          },
          "xml": {
            "type": "string",
            "description": "Kompletne UBL XML (Invoice alebo CreditNote)",
            "example": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>..."
          }
        }
      },
      "SendDocumentResponse": {
        "type": "object",
        "properties": {
          "documentId": {
            "type": "string",
            "example": "clx1234abcdef",
            "description": "Interne ID dokumentu"
          },
          "messageId": {
            "type": "string",
            "example": "msg-uuid-here",
            "description": "Peppol messageId"
          },
          "status": {
            "type": "string",
            "enum": [
              "SENT",
              "SENT_DB_PENDING"
            ],
            "example": "SENT",
            "description": "`SENT` = HTTP 201 (delivered + local DB persisted). `SENT_DB_PENDING` = HTTP 202 (delivered via Peppol but local DB write failed; reconciled by the post-send cron). Idempotent retry of the same `Idempotency-Key` after `SENT_DB_PENDING` will replay the same response."
          },
          "payloadSha256": {
            "type": "string",
            "example": "a1b2c3d4e5f60718...",
            "description": "Lowercase-hex SHA-256 of the canonical UBL XML bytes that were placed on the AS4 wire. Receivers can verify the payload they received off Peppol matches what we logged on send. Only present in `SENT` (201) responses; absent in `SENT_DB_PENDING` (202) because the DB persist that captures it failed."
          },
          "warning": {
            "type": "string",
            "description": "Human-readable explanation when `status=SENT_DB_PENDING` — describes the partial-failure mode and what reconciliation will do.",
            "example": "Document delivered via Peppol but local status update failed — will be reconciled automatically"
          }
        }
      },
      "PeppolValidationErrorResponse": {
        "type": "object",
        "description": "422 response shape for Peppol/EN16931 schematron failures. `/documents/send` emits `VALIDATION_FAILED` when the ion-docval run returns schematron errors; other endpoints use the unified `VALIDATION_ERROR` code.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "VALIDATION_FAILED",
                  "VALIDATION_ERROR"
                ],
                "example": "VALIDATION_FAILED"
              },
              "message": {
                "type": "string",
                "example": "Document failed Peppol validation"
              },
              "details": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "example": [
                  "[BR-01] An Invoice shall have a Specification identifier."
                ]
              }
            }
          }
        }
      },
      "PartyResponse": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "example": "Moja firma s.r.o."
          },
          "ico": {
            "type": "string",
            "example": "12345678"
          },
          "dic": {
            "type": "string",
            "example": "2020123456"
          },
          "icDph": {
            "type": "string",
            "example": "SK2020123456"
          },
          "address": {
            "type": "object",
            "properties": {
              "street": {
                "type": "string",
                "example": "Hlavna 1"
              },
              "city": {
                "type": "string",
                "example": "Bratislava"
              },
              "zip": {
                "type": "string",
                "example": "81101"
              },
              "country": {
                "type": "string",
                "example": "SK"
              }
            }
          },
          "peppolId": {
            "type": "string",
            "example": "0245:12345678"
          }
        }
      },
      "LineItemResponse": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": "number"
          },
          "unit": {
            "type": "string"
          },
          "unitPrice": {
            "type": "number"
          },
          "vatRate": {
            "type": "number"
          },
          "vatCategory": {
            "type": "string",
            "example": "S"
          },
          "lineTotal": {
            "type": "number"
          }
        }
      },
      "DocumentResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "number": {
            "type": "string",
            "example": "FAK-2026-0001"
          },
          "status": {
            "type": "string",
            "example": "received"
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ]
          },
          "docType": {
            "type": "string",
            "example": "invoice"
          },
          "issueDate": {
            "type": "string",
            "format": "date-time"
          },
          "dueDate": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "supplier": {
            "$ref": "#/components/schemas/PartyResponse"
          },
          "customer": {
            "$ref": "#/components/schemas/PartyResponse"
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/LineItemResponse"
            }
          },
          "totals": {
            "type": "object",
            "properties": {
              "withoutVat": {
                "type": "number"
              },
              "vat": {
                "type": "number"
              },
              "withVat": {
                "type": "number"
              }
            }
          },
          "peppolMessageId": {
            "type": "string",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InboxListResponse": {
        "type": "object",
        "properties": {
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DocumentResponse"
            }
          },
          "total": {
            "type": "integer",
            "example": 42
          },
          "limit": {
            "type": "integer",
            "example": 20
          },
          "offset": {
            "type": "integer",
            "example": 0
          },
          "nextCursor": {
            "type": "string",
            "nullable": true,
            "description": "Opaque cursor to fetch the next page. null when no more results.",
            "example": "eyJpZCI6ImNseDlhYmMxMjMifQ=="
          }
        }
      },
      "OutboxListResponse": {
        "type": "object",
        "properties": {
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DocumentResponse"
            }
          },
          "total": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          }
        }
      },
      "InboxDocumentDetailResponse": {
        "type": "object",
        "description": "Inbox document detail. `document` and `payload` are siblings (payload is NOT nested inside document). `document` matches the normalized shape from `lib/api/format.ts#formatInvoice` — supplier/customer are nested PartyResponse objects (supplier.peppolId carries the original sender).",
        "properties": {
          "document": {
            "$ref": "#/components/schemas/DocumentResponse"
          },
          "payload": {
            "type": "string",
            "nullable": true,
            "description": "UBL XML content (null if not yet retrieved from storage). Starts with `<?xml...?>`."
          }
        }
      },
      "AcknowledgeResponse": {
        "type": "object",
        "properties": {
          "documentId": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "ACKNOWLEDGED"
            ]
          },
          "acknowledgedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AccountResponse": {
        "type": "object",
        "properties": {
          "firm": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "example": "Moja firma s.r.o."
              },
              "ico": {
                "type": "string",
                "nullable": true,
                "example": "12345678"
              },
              "peppolId": {
                "type": "string",
                "nullable": true,
                "example": "0245:12345678"
              },
              "peppolStatus": {
                "type": "string",
                "example": "active"
              }
            }
          },
          "plan": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "example": "api-enterprise"
              },
              "status": {
                "type": "string",
                "enum": [
                  "active",
                  "expired"
                ]
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "outbound": {
                "type": "integer",
                "description": "Pocet odoslanych dokumentov"
              },
              "inbound": {
                "type": "integer",
                "description": "Pocet prijatych dokumentov"
              }
            }
          }
        }
      },
      "ExtractResponse": {
        "type": "object",
        "properties": {
          "extraction": {
            "type": "object",
            "description": "Extrahovane data z dokumentu"
          },
          "ubl_xml": {
            "type": "string",
            "description": "Vygenerovane UBL XML z extrahovanych dat"
          },
          "confidence": {
            "type": "string",
            "enum": [
              "high",
              "medium",
              "low"
            ],
            "description": "Overall confidence bucket."
          },
          "confidence_scores": {
            "type": "object",
            "description": "Per-field confidence (0..1). Fields: vendor_name, vendor_ico, invoice_number, issue_date, due_date, items, subtotal, vat_total, total, currency, iban, variable_symbol.",
            "additionalProperties": {
              "type": "number"
            }
          },
          "needs_review": {
            "type": "boolean",
            "description": "True when confidence is low or medium — surface a human review UI."
          },
          "file_name": {
            "type": "string",
            "example": "faktura-2026-001.pdf"
          }
        }
      },
      "ExtractErrorResponse": {
        "oneOf": [
          {
            "type": "object",
            "description": "Structured error for /extract — Gemini extraction failure.",
            "properties": {
              "error": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "example": "EXTRACTION_FAILED"
                  },
                  "message": {
                    "type": "string",
                    "example": "Extraction failed"
                  },
                  "details": {
                    "type": "string",
                    "description": "Gemini error detail"
                  }
                }
              }
            }
          },
          {
            "type": "object",
            "description": "Legacy plain-string error for /extract — UBL generation failure path.",
            "properties": {
              "error": {
                "type": "string",
                "example": "UBL generation failed"
              },
              "details": {
                "type": "string"
              },
              "extraction": {
                "type": "object",
                "description": "Partial extraction payload (so caller can retry with manual edits)"
              }
            }
          }
        ]
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "example": "https://vas-system.sk/webhooks/epostak",
            "description": "HTTPS URL (http rejected, private/loopback/metadata addresses rejected by SSRF filter)"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "document.created",
                "document.sent",
                "document.received",
                "document.validated",
                "document.delivered",
                "document.rejected",
                "document.response_received"
              ]
            },
            "description": "Subscribed events. If omitted, subscribes to all 7.",
            "example": [
              "document.received",
              "document.sent"
            ]
          }
        }
      },
      "UpdateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "document.created",
                "document.sent",
                "document.received",
                "document.validated",
                "document.delivered",
                "document.rejected",
                "document.response_received"
              ]
            }
          },
          "isActive": {
            "type": "boolean"
          }
        }
      },
      "WebhookResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "isActive": {
            "type": "boolean"
          },
          "failedAttempts": {
            "type": "integer",
            "description": "Consecutive failure counter. Webhooks auto-disable after 10 consecutive failures.",
            "example": 0
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookDetailResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookResponse"
          },
          {
            "type": "object",
            "properties": {
              "secret": {
                "type": "string",
                "description": "HMAC-SHA256 signing secret (vrateny iba pri vytvoreni)",
                "example": "a3f8...hex64chars"
              }
            }
          }
        ]
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "webhookId": {
            "type": "string"
          },
          "event": {
            "type": "string",
            "example": "document.received"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "FAILED",
              "RETRYING"
            ],
            "example": "SUCCESS",
            "description": "Delivery status (UPPERCASE enum)"
          },
          "attempts": {
            "type": "integer",
            "example": 1
          },
          "responseStatus": {
            "type": "integer",
            "nullable": true,
            "example": 200
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookWithDeliveriesResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/WebhookResponse"
          },
          {
            "type": "object",
            "properties": {
              "deliveries": {
                "type": "array",
                "description": "Poslednych 20 doruceni",
                "items": {
                  "$ref": "#/components/schemas/WebhookDelivery"
                }
              }
            }
          }
        ]
      },
      "WebhookListResponse": {
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookResponse"
            }
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "description": "Standard error envelope for `/api/v1/*` endpoints (excluding `/auth/token|renew|revoke` which use `PeppolErrorResponse`, and `/oauth/token` which uses RFC 6749 `OAuthErrorResponse`). The `requestId` echoes the `X-Request-Id` response header — quote it in support tickets.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message",
              "requestId"
            ],
            "properties": {
              "code": {
                "type": "string",
                "description": "Machine-readable error code. Known codes:\n- `VALIDATION_ERROR` (422) — Zod or manual schema violation\n- `UNPROCESSABLE_ENTITY` (422) — business rule rejection\n- `NOT_FOUND` (404)\n- `FORBIDDEN` (403) — plan/ownership denied\n- `CONFLICT` (409) — idempotency in-flight or unique constraint\n- `BAD_REQUEST` (400) — malformed body/params\n- `INVALID_PARAM` (400)\n- `PAYLOAD_TOO_LARGE` (413)\n- `IDEMPOTENCY_KEY_MISMATCH` (422) — strict mode: same key replayed with different body\n- `IDEMPOTENCY_STORE_UNAVAILABLE` (503) — Redis down\n- `VALIDATION_SERVICE_UNAVAILABLE` (503) — ion-docval down\n- `SEND_FAILED` (502) — Peppol AP dispatch failed, retryable\n- `SERVICE_UNAVAILABLE` (503) — upstream Gemini/etc down\n- `INTERNAL_ERROR` (500)",
                "example": "VALIDATION_ERROR"
              },
              "message": {
                "type": "string",
                "example": "receiverPeppolId is required"
              },
              "requestId": {
                "type": "string",
                "format": "uuid",
                "description": "Mirror of the `X-Request-Id` response header — for tracing."
              },
              "details": {
                "description": "Optional structured details (e.g., Zod error array, Peppol schematron rule IDs)"
              },
              "conflictTarget": {
                "type": "string",
                "description": "On 409 from a unique-constraint collision: comma-joined target columns (e.g. `firm_id,idempotency_key`)."
              }
            }
          }
        }
      },
      "ForbiddenErrorResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ErrorResponse"
          }
        ],
        "description": "403 variant of `ErrorResponse`. The 401/403 responses also include `WWW-Authenticate: Bearer error=\"invalid_token\"|\"insufficient_scope\" scope=\"<required-scope>\"` per RFC 6750 — clients should parse `scope=\"…\"` to learn what scope is required."
      },
      "SimpleErrorResponse": {
        "type": "object",
        "description": "Legacy non-canonical error shape used by a few multipart upload endpoints (e.g. `/extract`). Standard endpoints use `ErrorResponse`.",
        "properties": {
          "error": {
            "type": "string",
            "example": "file is required (multipart/form-data field: file)"
          }
        }
      },
      "PeppolErrorResponse": {
        "type": "object",
        "description": "Error envelope used by the JWT auth endpoints (`/auth/token`, `/auth/renew`, `/auth/revoke`) and any `/sapi/v1/*` endpoint. Different from `ErrorResponse` — designed for Peppol Access Point error semantics with retry hints.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "category",
              "code",
              "message",
              "retryable",
              "correlation_id"
            ],
            "properties": {
              "category": {
                "type": "string",
                "enum": [
                  "AUTH",
                  "VALIDATION",
                  "PROCESSING",
                  "TEMPORARY",
                  "PERMANENT"
                ]
              },
              "code": {
                "type": "string",
                "example": "SAPI-AUTH-001",
                "description": "Stable machine-readable code (e.g. `SAPI-AUTH-001` invalid creds, `SAPI-AUTH-008` scope denied, `SAPI-AUTH-099` internal)."
              },
              "message": {
                "type": "string",
                "example": "Invalid client credentials"
              },
              "retryable": {
                "type": "boolean",
                "description": "True for 5xx/429 — caller may retry with backoff."
              },
              "correlation_id": {
                "type": "string",
                "format": "uuid"
              },
              "details": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "field": {
                      "type": "string"
                    },
                    "issue": {
                      "type": "string"
                    },
                    "value": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "JwtTokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "client_id",
          "client_secret"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "client_credentials"
            ]
          },
          "client_id": {
            "type": "string",
            "description": "The client_id shown with the key. It must match the API key row UUID or displayed key prefix; it is not the sk_live_*/sk_int_* secret."
          },
          "client_secret": {
            "type": "string",
            "description": "The full `sk_live_*` or `sk_int_*` API key."
          },
          "scope": {
            "type": "string",
            "description": "Optional space-separated subset of the key's allowed scopes. If omitted, the JWT inherits the key's full scope set (or `*` for wildcard keys)."
          }
        }
      },
      "JwtTokenResponse": {
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "refresh_token",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Short-lived JWT (RS256, 15 min TTL). Use as `Authorization: Bearer <token>` on all `/api/v1/*` endpoints."
          },
          "token_type": {
            "type": "string",
            "enum": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "example": 900
          },
          "refresh_token": {
            "type": "string",
            "description": "Refresh token (30 day TTL). Single-use — rotated on each `/auth/renew` call."
          },
          "scope": {
            "type": "string",
            "example": "documents:send documents:read"
          }
        }
      },
      "JwtRenewRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "refresh_token"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "refresh_token"
            ]
          },
          "refresh_token": {
            "type": "string"
          }
        }
      },
      "JwtRevokeRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "Either a JWT access token (starts with `eyJ`) or a refresh token."
          },
          "token_type_hint": {
            "type": "string",
            "enum": [
              "access_token",
              "refresh_token"
            ],
            "description": "Optional hint — the server detects type automatically."
          }
        }
      },
      "StatusHistoryEntry": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "example": "SENT"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "detail": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "DocumentStatusResponse": {
        "type": "object",
        "description": "Stav dokumentu z pohľadu volajúceho — `direction` rozlišuje, či ide o **inbound** riadok (prijímateľ) alebo **outbound** riadok (odosielateľ). To isté Peppol message ID môže existovať na oboch stranách (prijímateľ aj odosielateľ majú vlastný riadok), ale ich timestampy a stavy sú **nezávislé**:\n\n- `deliveredAt` a `acknowledgedAt` na **inbound** riadku nastavuje prijímateľ cez `POST /documents/{id}/mark` resp. `POST /documents/inbox/{id}/acknowledge`. Tieto signály sú **lokálne** — Peppol BIS Billing 3.0 pre ne nemá ekvivalentnú správu, takže sa neposielajú odosielateľovi.\n- `acknowledgedAt` na **outbound** riadku sa vyplní iba vtedy, keď cez Peppol AS4 dorazí Invoice Response (kódy AP / AB / RE / …) — to je sieťový signál vygenerovaný cez `POST /documents/{id}/respond`.\n- `deliveredAt` na **outbound** riadku sa nikdy nepropaguje z prijímateľa. Ak odosielateľ potrebuje vidieť, že prijímateľ doručil/spracoval dokument, prijímateľ musí volať `/respond` s vhodným kódom (`AP` = accepted, `IP` = in process, `RE` = rejected).",
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "example": "DELIVERED"
          },
          "documentType": {
            "type": "string",
            "nullable": true,
            "example": "invoice"
          },
          "direction": {
            "type": "string",
            "enum": ["inbound", "outbound"],
            "description": "`inbound` = my sme prijímateľ (faktúru sme dostali). `outbound` = my sme odosielateľ. Určuje sémantiku timestampov: kto čo nastavuje a kedy."
          },
          "senderPeppolId": {
            "type": "string",
            "nullable": true
          },
          "receiverPeppolId": {
            "type": "string",
            "nullable": true
          },
          "statusHistory": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StatusHistoryEntry"
            }
          },
          "validationResult": {
            "nullable": true,
            "description": "`null` on success, or `{ errors: string[] }` when validation failed.",
            "type": "object",
            "properties": {
              "errors": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          },
          "deliveredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Inbound: nastavené prijímateľom cez `/mark state=delivered`. Outbound: vždy `null` — Peppol nemá delivery callback."
          },
          "acknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Inbound: nastavené prijímateľom cez `/acknowledge` alebo `/mark state=processed`. Outbound: nastavené v okamihu, keď nám AS4 doručí prijímateľov Invoice Response (cez `/respond`) — pokrýva celú sieťovú latenciu, takže timestamp sa typicky líši od inbound `acknowledgedAt`."
          },
          "invoiceResponseStatus": {
            "type": "string",
            "enum": [
              "AB",
              "IP",
              "UQ",
              "CA",
              "RE",
              "AP",
              "PD"
            ],
            "nullable": true,
            "description": "Buyer's last invoice response status (7 UBL-2005 codes)."
          },
          "peppolMessageId": {
            "type": "string",
            "nullable": true,
            "description": "Identifikátor správy v Peppol/AS4. Rovnaká hodnota ako `as4MessageId` (alias)."
          },
          "as4MessageId": {
            "type": "string",
            "nullable": true,
            "description": "Identifikátor AS4 správy. Rovnaká hodnota ako `peppolMessageId` — oba aliasy sú v odpovedi pre uľahčenie konzumácie (detail faktúry vracia `peppolMessageId`, AS4-orientovaní integrátori používajú `as4MessageId`)."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "updatedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DocumentEvidenceResponse": {
        "type": "object",
        "properties": {
          "documentId": {
            "type": "string"
          },
          "as4Receipt": {
            "type": "object",
            "nullable": true,
            "description": "AS4 receipt od pristupoveho bodu prijemcu"
          },
          "mlrDocument": {
            "type": "string",
            "nullable": true,
            "description": "Message Level Response XML"
          },
          "invoiceResponse": {
            "type": "object",
            "nullable": true,
            "description": "Invoice Response od prijemcu",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "AB",
                  "IP",
                  "UQ",
                  "CA",
                  "RE",
                  "AP",
                  "PD"
                ],
                "nullable": true
              },
              "document": {
                "type": "string",
                "description": "Invoice Response UBL XML"
              }
            }
          },
          "tdd": {
            "type": "object",
            "nullable": true,
            "description": "Populated when the document was reported to FS SR as a Tax Data Document.",
            "properties": {
              "reportedAt": {
                "type": "string",
                "format": "date-time"
              },
              "reported": {
                "type": "boolean"
              }
            }
          },
          "sentAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "deliveredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "InvoiceResponseItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "responseCode": {
            "type": "string",
            "enum": [
              "AB",
              "IP",
              "UQ",
              "CA",
              "RE",
              "AP",
              "PD"
            ],
            "description": "AB=Accepted Billing, IP=In Process, UQ=Under Query, CA=Conditionally Accepted, RE=Rejected, AP=Accepted, PD=Paid"
          },
          "note": {
            "type": "string",
            "nullable": true
          },
          "senderPeppolId": {
            "type": "string"
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "InvoiceResponsesListResponse": {
        "type": "object",
        "properties": {
          "documentId": {
            "type": "string"
          },
          "responses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/InvoiceResponseItem"
            }
          }
        }
      },
      "InvoiceRespondRequest": {
        "type": "object",
        "required": [
          "status"
        ],
        "description": "Request body for POST /documents/{id}/respond. Field is `status` (not `responseCode`). 7 valid values mapping to UBL ApplicationResponse codes.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "AB",
              "IP",
              "UQ",
              "CA",
              "RE",
              "AP",
              "PD"
            ],
            "description": "AB=accepted_billing, IP=in_process, UQ=under_query, CA=conditionally_accepted, RE=rejected, AP=accepted, PD=paid"
          },
          "note": {
            "type": "string",
            "maxLength": 500,
            "example": "Faktura akceptovana",
            "description": "Optional note, truncated to 500 characters."
          }
        }
      },
      "InvoiceRespondResponse": {
        "type": "object",
        "description": "Returned with HTTP 200 when the AS4 dispatch succeeded, 202 when the response XML was persisted but dispatch failed and will be retried async.",
        "properties": {
          "documentId": {
            "type": "string"
          },
          "responseStatus": {
            "type": "string",
            "enum": [
              "AB",
              "IP",
              "UQ",
              "CA",
              "RE",
              "AP",
              "PD"
            ]
          },
          "respondedAt": {
            "type": "string",
            "format": "date-time"
          },
          "peppolMessageId": {
            "type": "string",
            "nullable": true
          },
          "dispatchStatus": {
            "type": "string",
            "enum": [
              "sent",
              "failed_queued"
            ]
          },
          "dispatchError": {
            "type": "string",
            "description": "Populated only when dispatchStatus=failed_queued (HTTP 202)"
          }
        }
      },
      "ValidateDocumentResponse": {
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "example": true
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Upozornenia (nie fatalne chyby)"
          },
          "ubl": {
            "type": "string",
            "nullable": true,
            "description": "Vygenerovane UBL XML (len pri JSON mode)"
          }
        }
      },
      "PreflightResponse": {
        "type": "object",
        "description": "Pre-flight lookup. Three booleans use tri-state (true|false|null) — null means 'unable to determine' (e.g., SMP timeout, no doctype filter specified).",
        "properties": {
          "receiverPeppolId": {
            "type": "string",
            "example": "0245:12345678"
          },
          "recipientAcceptsDocumentType": {
            "type": "boolean",
            "nullable": true,
            "description": "Tri-state: true = doctype in SMP, false = not advertised, null = no documentType probed or lookup inconclusive."
          },
          "validationPassed": {
            "type": "boolean",
            "nullable": true,
            "description": "Tri-state: true = schematron clean, false = errors, null = skipped (no UBL payload to validate)."
          },
          "canSend": {
            "type": "boolean",
            "nullable": true,
            "description": "Overall verdict. false blocks the send, null = caller should still attempt (non-blocking warning)."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "errors": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ConvertDocumentRequest": {
        "type": "object",
        "required": [
          "input_format",
          "output_format",
          "document"
        ],
        "properties": {
          "input_format": {
            "type": "string",
            "enum": [
              "json",
              "ubl"
            ],
            "description": "Format vstupneho dokumentu"
          },
          "output_format": {
            "type": "string",
            "enum": [
              "json",
              "ubl"
            ],
            "description": "Format vystupneho dokumentu"
          },
          "document": {
            "oneOf": [
              {
                "type": "object"
              },
              {
                "type": "string"
              }
            ],
            "description": "JSON objekt (pre input_format=json) alebo UBL XML string (pre input_format=ubl)"
          }
        }
      },
      "ConvertDocumentResponse": {
        "type": "object",
        "properties": {
          "output_format": {
            "type": "string",
            "enum": [
              "json",
              "ubl"
            ]
          },
          "document": {
            "description": "Vysledok konverzie — UBL XML string (pre output_format=ubl) alebo JSON objekt (pre output_format=json)"
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "SmpParticipantCapability": {
        "type": "object",
        "properties": {
          "documentTypeId": {
            "type": "string"
          },
          "processId": {
            "type": "string"
          },
          "transportProfile": {
            "type": "string",
            "example": "peppol-transport-as4-v2_0"
          }
        }
      },
      "SmpLookupResponse": {
        "type": "object",
        "properties": {
          "peppolId": {
            "type": "string",
            "example": "0245:12345678"
          },
          "name": {
            "type": "string",
            "nullable": true,
            "example": "Moja firma s.r.o."
          },
          "country": {
            "type": "string",
            "nullable": true,
            "example": "SK"
          },
          "capabilities": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SmpParticipantCapability"
            }
          }
        }
      },
      "PeppolDirectoryEntry": {
        "type": "object",
        "properties": {
          "peppolId": {
            "type": "string",
            "example": "0245:12345678"
          },
          "name": {
            "type": "string",
            "example": "Moja firma s.r.o."
          },
          "country": {
            "type": "string",
            "example": "SK"
          },
          "registeredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "PeppolDirectorySearchResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PeppolDirectoryEntry"
            }
          },
          "total": {
            "type": "integer",
            "example": 42
          },
          "page": {
            "type": "integer",
            "example": 1
          },
          "page_size": {
            "type": "integer",
            "example": 20
          }
        }
      },
      "CompanyLookupResponse": {
        "type": "object",
        "description": "Slovenská firma podľa IČO. Dáta pochádzajú z denného exportu Finančnej správy SR (`fs_tax_subjects` + `fs_vat_violations`). 200 odpoveď znamená, že IČO je v exporte FS — buď ako aktívny subjekt (`active: true`), alebo ako deaktivovaný (`active: false`, vyplnený `cancelled_at`). Skutočne neznáme IČO vracia 404.",
        "properties": {
          "name": {
            "type": "string",
            "example": "Moja firma s.r.o."
          },
          "tax_id_1": {
            "type": "string",
            "description": "IČO (8 číslic, padded zľava nulami).",
            "example": "12345678"
          },
          "tax_id_2": {
            "type": "string",
            "nullable": true,
            "description": "DIČ.",
            "example": "2020123456"
          },
          "tax_id_3": {
            "type": "string",
            "nullable": true,
            "description": "IČ DPH (`SK<dic>` ak je platca).",
            "example": "SK2020123456"
          },
          "address_street": {
            "type": "string",
            "nullable": true
          },
          "address_city": {
            "type": "string",
            "nullable": true
          },
          "address_zip": {
            "type": "string",
            "nullable": true
          },
          "address_country": {
            "type": "string",
            "example": "SK"
          },
          "legal_form": {
            "type": "string",
            "nullable": true,
            "description": "Heuristika z názvu (suffix `s.r.o.` / `a.s.` / `o.z.` …). `živnosť` znamená subjekt s IČO bez právnickej formy v názve (typicky SZČO). Ak vidíte `živnosť` na občianskom združení / nadácii, prosíme nahláste konkrétne IČO — regex doplníme.",
            "enum": [
              "s.r.o.",
              "a.s.",
              "j.s.a.",
              "k.s.",
              "v.o.s.",
              "družstvo",
              "š.p.",
              "n.o.",
              "n.f.",
              "o.z.",
              "živnosť",
              "FO"
            ]
          },
          "vat_reg_type": {
            "type": "string",
            "nullable": true,
            "description": "§-paragraf zákona č. 222/2004 Z.z. o DPH, podľa ktorého je subjekt registrovaný. Pozri `vat_reg_type_description` pre slovenský popis.",
            "enum": ["§4", "§4b", "§5", "§7", "§7a"]
          },
          "vat_reg_type_description": {
            "type": "string",
            "nullable": true,
            "description": "Slovenský popis hodnoty `vat_reg_type`."
          },
          "dph_reg_from": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Dátum registrácie za platiteľa DPH (`PLAT_DPH_OD` z FS feedu)."
          },
          "is_vat_payer": {
            "type": "boolean",
            "description": "Odvodený flag. `true` keď: subjekt je `active`, má `dph_reg_from` a žiadna ds_dphv deregistrácia neprekrýva registráciu. Pre presnú interpretáciu zákazníckeho stavu DPH stále preferujte vlastnú kontrolu nad `vat_violations` + `dph_reg_from`."
          },
          "active": {
            "type": "boolean",
            "description": "`true` keď subjekt bol v poslednom dennom dumpe FS. `false` znamená že FS subjekt vyradila → typicky zaniknutá / zrušená firma.",
            "default": true
          },
          "cancelled_at": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "description": "Dátum, kedy bol subjekt naposledy videný v FS dumpe pred deaktiváciou. Vyplnené iba ak `active: false`."
          },
          "vat_violations": {
            "type": "array",
            "description": "Históriai aktuálne sankcie DPH. `ds_dphv` = deregistrácia za porušenie zákona o DPH (historický KYC indikátor). `ds_dphz` = aktuálne sankcionovaný platca DPH (otvorený dlh / porušenie). Prázdne pole = subjekt v sankčných zoznamoch FS nie je.",
            "items": {
              "type": "object",
              "properties": {
                "source": {
                  "type": "string",
                  "enum": ["ds_dphv", "ds_dphz"]
                },
                "year_violation": {
                  "type": "integer",
                  "nullable": true
                },
                "deletion_date": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                },
                "publication_date": {
                  "type": "string",
                  "format": "date",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "FirmSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string",
            "example": "Example s.r.o."
          },
          "ico": {
            "type": "string",
            "example": "12345678"
          },
          "peppol_id": {
            "type": "string",
            "example": "0245:12345678",
            "nullable": true
          },
          "peppol_status": {
            "type": "string",
            "enum": [
              "ACTIVE",
              "PENDING",
              "NONE"
            ],
            "example": "ACTIVE"
          }
        }
      },
      "FirmsListResponse": {
        "type": "object",
        "properties": {
          "firms": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FirmSummary"
            }
          }
        }
      },
      "FirmDetailResponse": {
        "allOf": [
          {
            "$ref": "#/components/schemas/FirmSummary"
          },
          {
            "type": "object",
            "properties": {
              "dic": {
                "type": "string",
                "nullable": true
              },
              "icDph": {
                "type": "string",
                "nullable": true
              },
              "address": {
                "type": "object",
                "properties": {
                  "street": {
                    "type": "string"
                  },
                  "city": {
                    "type": "string"
                  },
                  "zip": {
                    "type": "string"
                  },
                  "country": {
                    "type": "string"
                  }
                }
              },
              "peppolIdentifiers": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "scheme": {
                      "type": "string"
                    },
                    "identifier": {
                      "type": "string"
                    }
                  }
                }
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        ]
      },
      "PeppolIdentifierResponse": {
        "type": "object",
        "properties": {
          "peppolId": {
            "type": "string",
            "example": "0245:12345678"
          },
          "scheme": {
            "type": "string",
            "example": "0245"
          },
          "identifier": {
            "type": "string",
            "example": "12345678"
          },
          "registeredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookQueueEvent": {
        "type": "object",
        "description": "Snake_case keys on `/webhook-queue/all`. The per-firm `/webhook-queue` route returns the same shape.",
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid",
            "example": "a3d2...-uuid"
          },
          "firm_id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "type": "string",
            "example": "document.received"
          },
          "payload": {
            "type": "object",
            "description": "Event-specific payload"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookQueueResponse": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookQueueEvent"
            }
          },
          "count": {
            "type": "integer",
            "example": 5,
            "description": "Pocet vratenych udalosti"
          }
        }
      },
      "StatisticsResponse": {
        "type": "object",
        "properties": {
          "period": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "format": "date"
              },
              "to": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "outbound": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "example": 120
              },
              "delivered": {
                "type": "integer",
                "example": 115
              },
              "failed": {
                "type": "integer",
                "example": 5
              }
            }
          },
          "inbound": {
            "type": "object",
            "properties": {
              "total": {
                "type": "integer",
                "example": 48
              },
              "acknowledged": {
                "type": "integer",
                "example": 40
              },
              "pending": {
                "type": "integer",
                "example": 8
              }
            }
          }
        }
      },
      "AssignFirmRequest": {
        "type": "object",
        "required": [
          "ico"
        ],
        "properties": {
          "ico": {
            "type": "string",
            "minLength": 8,
            "maxLength": 8,
            "example": "12345678",
            "description": "8-digit company registration number (ICO)"
          }
        }
      },
      "AssignFirmResponse": {
        "type": "object",
        "properties": {
          "firm": {
            "$ref": "#/components/schemas/FirmSummary"
          },
          "status": {
            "type": "string",
            "example": "active"
          }
        }
      },
      "BatchAssignFirmsRequest": {
        "type": "object",
        "required": [
          "icos"
        ],
        "properties": {
          "icos": {
            "type": "array",
            "items": {
              "type": "string",
              "minLength": 8,
              "maxLength": 8
            },
            "maxItems": 50,
            "example": [
              "12345678",
              "87654321"
            ]
          }
        }
      },
      "BatchAssignFirmsResultItem": {
        "type": "object",
        "properties": {
          "ico": {
            "type": "string",
            "example": "12345678"
          },
          "firm": {
            "$ref": "#/components/schemas/FirmSummary"
          },
          "status": {
            "type": "string",
            "example": "active"
          },
          "error": {
            "type": "string",
            "example": "not_found",
            "nullable": true
          },
          "message": {
            "type": "string",
            "example": "Firm not found",
            "nullable": true
          }
        }
      },
      "BatchAssignFirmsResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchAssignFirmsResultItem"
            }
          }
        }
      },
      "CrossFirmInboxDocument": {
        "type": "object",
        "properties": {
          "firm_id": {
            "type": "string",
            "format": "uuid"
          },
          "firm_name": {
            "type": "string",
            "example": "Example s.r.o."
          },
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "number": {
            "type": "string",
            "example": "FAK-2026-0001"
          },
          "status": {
            "type": "string",
            "enum": [
              "RECEIVED",
              "ACKNOWLEDGED"
            ]
          },
          "sender_peppol_id": {
            "type": "string",
            "example": "0245:99887766"
          },
          "sender_name": {
            "type": "string",
            "example": "Supplier s.r.o."
          },
          "amount": {
            "type": "number",
            "example": 1210
          },
          "currency": {
            "type": "string",
            "example": "EUR"
          },
          "received_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CrossFirmInboxResponse": {
        "type": "object",
        "properties": {
          "documents": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CrossFirmInboxDocument"
            }
          },
          "total": {
            "type": "integer",
            "example": 120
          },
          "limit": {
            "type": "integer",
            "example": 50
          },
          "offset": {
            "type": "integer",
            "example": 0
          }
        }
      },
      "CrossFirmWebhookEvent": {
        "type": "object",
        "properties": {
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "firm_id": {
            "type": "string",
            "format": "uuid"
          },
          "event": {
            "type": "string",
            "example": "document.received"
          },
          "payload": {
            "type": "object"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "CrossFirmWebhookQueueResponse": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CrossFirmWebhookEvent"
            }
          },
          "count": {
            "type": "integer",
            "example": 5
          }
        }
      },
      "BatchAckWebhookRequest": {
        "type": "object",
        "required": [
          "event_ids"
        ],
        "properties": {
          "event_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "maxItems": 1000,
            "example": [
              "uuid-1",
              "uuid-2"
            ]
          }
        }
      },
      "BatchAckWebhookResponse": {
        "type": "object",
        "properties": {
          "acknowledged": {
            "type": "integer",
            "example": 42
          }
        }
      },
      "OAuthTokenRequest": {
        "type": "object",
        "required": [
          "grant_type",
          "code",
          "client_id",
          "client_secret",
          "redirect_uri"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code"
            ],
            "example": "authorization_code"
          },
          "code": {
            "type": "string",
            "example": "auth_code_here"
          },
          "client_id": {
            "type": "string",
            "example": "client_id_here"
          },
          "client_secret": {
            "type": "string",
            "example": "client_secret_here"
          },
          "redirect_uri": {
            "type": "string",
            "format": "uri",
            "example": "https://example.com/callback"
          },
          "code_verifier": {
            "type": "string",
            "description": "PKCE code verifier (optional)",
            "example": "pkce_verifier_here"
          }
        }
      },
      "OAuthTokenResponse": {
        "type": "object",
        "required": [
          "client_id",
          "client_secret",
          "secret_type",
          "token_type",
          "scope",
          "firm_id"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "description": "Identifier to send as client_id when exchanging this secret at /api/v1/auth/token.",
            "example": "sk_int_abc...xyz"
          },
          "client_secret": {
            "type": "string",
            "description": "New sk_int_* secret. Store once; it is not shown again.",
            "example": "sk_int_abc123"
          },
          "secret_type": {
            "type": "string",
            "enum": [
              "sk_int"
            ]
          },
          "token_type": {
            "type": "string",
            "enum": [
              "client_secret"
            ]
          },
          "scope": {
            "type": "string",
            "example": "firms:manage documents:send documents:read"
          },
          "firm_id": {
            "type": "string",
            "format": "uuid"
          },
          "firm_name": {
            "type": "string",
            "example": "Example s.r.o."
          },
          "firm_ico": {
            "type": "string",
            "example": "12345678"
          }
        }
      },
      "OAuthErrorResponse": {
        "type": "object",
        "description": "RFC 6749 OAuth 2.0 error response. Uses top-level `error` and `error_description` strings, not the `error: { code, message }` envelope used everywhere else in this API.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "enum": [
              "invalid_request",
              "invalid_client",
              "invalid_grant",
              "unauthorized_client",
              "unsupported_grant_type",
              "invalid_scope"
            ],
            "example": "invalid_grant"
          },
          "error_description": {
            "type": "string",
            "example": "Authorization code is invalid or expired"
          }
        }
      },
      "AuthStatusResponse": {
        "type": "object",
        "properties": {
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string",
                "example": "Production integration"
              },
              "prefix": {
                "type": "string",
                "example": "sk_live_abc123"
              },
              "permissions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "active": {
                "type": "boolean"
              },
              "createdAt": {
                "type": "string",
                "format": "date-time"
              },
              "lastUsedAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "firm": {
            "type": "object",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              },
              "peppolStatus": {
                "type": "string",
                "example": "active"
              }
            }
          },
          "plan": {
            "type": "object",
            "properties": {
              "name": {
                "type": "string",
                "example": "api-enterprise"
              },
              "expiresAt": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "active": {
                "type": "boolean"
              }
            }
          },
          "rateLimit": {
            "type": "object",
            "properties": {
              "perMinute": {
                "type": "integer",
                "example": 200
              },
              "window": {
                "type": "string",
                "example": "60s"
              }
            }
          },
          "integrator": {
            "type": "object",
            "nullable": true,
            "description": "Present only for sk_int_* keys",
            "properties": {
              "id": {
                "type": "string",
                "format": "uuid"
              }
            }
          }
        }
      },
      "RotateSecretResponse": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "New full API key, shown once.",
            "example": "sk_live_abc...xyz"
          },
          "prefix": {
            "type": "string",
            "example": "sk_live_abc123"
          },
          "message": {
            "type": "string",
            "example": "Key rotated. Save it — it will not be shown again. The previous key is now inactive."
          }
        }
      },
      "ReportingSubmissionsResponse": {
        "type": "object",
        "required": [
          "items",
          "total",
          "limit",
          "offset"
        ],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReportingSubmissionItem"
            }
          },
          "total": {
            "type": "integer",
            "example": 24
          },
          "limit": {
            "type": "integer",
            "example": 20
          },
          "offset": {
            "type": "integer",
            "example": 0
          }
        }
      },
      "ReportingSubmissionItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "report_type": {
            "type": "string",
            "enum": [
              "EUSR",
              "TSR"
            ]
          },
          "period": {
            "type": "object",
            "properties": {
              "from": {
                "type": "string",
                "format": "date"
              },
              "to": {
                "type": "string",
                "format": "date"
              }
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "sent",
              "failed",
              "pending",
              "submitting"
            ]
          },
          "message_id": {
            "type": "string",
            "nullable": true,
            "description": "Peppol AS4 message ID (null if not yet submitted)."
          },
          "submitted_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "has_error": {
            "type": "boolean"
          }
        }
      },
      "BatchSendItem": {
        "type": "object",
        "description": "Same shape as the POST /documents/send body (JSON or XML mode), plus an optional per-item idempotencyKey.",
        "properties": {
          "idempotencyKey": {
            "type": "string",
            "description": "Optional per-item idempotency key (propagated as `Idempotency-Key` header on the underlying single send)."
          }
        },
        "additionalProperties": true
      },
      "BatchSendRequest": {
        "type": "object",
        "required": [
          "items"
        ],
        "properties": {
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "$ref": "#/components/schemas/BatchSendItem"
            }
          }
        }
      },
      "BatchSendResponse": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "example": 50
          },
          "succeeded": {
            "type": "integer",
            "example": 48
          },
          "failed": {
            "type": "integer",
            "example": 2
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer",
                  "description": "0-based position in the input items[]"
                },
                "status": {
                  "type": "integer",
                  "description": "HTTP status of the underlying single send (201, 422, 502, ...)"
                },
                "result": {
                  "description": "Full response body from the underlying call (SendDocumentResponse or ErrorResponse)"
                }
              }
            }
          }
        }
      },
      "ParseResponse": {
        "type": "object",
        "description": "Full normalized invoice payload. `invoice` contains the UBL body in ePostak's canonical JSON shape. `extras` holds BG-24 attachment descriptors, notes, and other UBL-specific fields. `allowances` lists document-level charges/allowances.",
        "properties": {
          "invoice": {
            "type": "object",
            "description": "Normalized invoice payload — see `/documents/parse` route implementation for the full field list."
          },
          "extras": {
            "type": "object"
          },
          "allowances": {
            "type": "array",
            "items": {
              "type": "object"
            }
          }
        }
      },
      "MarkRequest": {
        "type": "object",
        "required": [
          "state"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "delivered",
              "processed",
              "failed",
              "read"
            ],
            "description": "delivered = external channel confirmed physical delivery; processed = buyer's ERP consumed the document; failed = terminal failure (blocks retries); read = user opened it (sets `read_at`)."
          },
          "note": {
            "type": "string",
            "description": "Optional audit note appended to statusHistory."
          }
        }
      },
      "MarkResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "state": {
            "type": "string",
            "example": "delivered"
          },
          "status": {
            "type": "string",
            "description": "Invoice status after transition"
          },
          "deliveredAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "acknowledgedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "readAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "DocumentEvent": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "eventType": {
            "type": "string",
            "example": "document.sent",
            "description": "One of: document.created, document.status_changed, document.approved, document.sent, document.send_failed, document.as4_delivered, document.mlr_received, document.response_received, document.response_sent, document.paid, document.fs_reported."
          },
          "actor": {
            "type": "string",
            "enum": [
              "system",
              "user",
              "api"
            ]
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "meta": {
            "description": "Event-specific metadata (peppolMessageId, toStatus, approvedBy, ...)"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DocumentEventsResponse": {
        "type": "object",
        "properties": {
          "documentId": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DocumentEvent"
            }
          },
          "pagination": {
            "type": "object",
            "properties": {
              "limit": {
                "type": "integer",
                "example": 20
              },
              "nextCursor": {
                "type": "string",
                "nullable": true
              },
              "hasMore": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "CapabilitiesRequest": {
        "type": "object",
        "required": [
          "participant"
        ],
        "properties": {
          "participant": {
            "type": "object",
            "required": [
              "scheme",
              "identifier"
            ],
            "properties": {
              "scheme": {
                "type": "string",
                "example": "0245",
                "description": "4-digit ISO 6523 ICD scheme (SK = 0245)"
              },
              "identifier": {
                "type": "string",
                "example": "2122701339"
              }
            }
          },
          "documentType": {
            "type": "string",
            "description": "BIS 3.0 document type ID or substring. When omitted, the response returns all supported types and `accepts=true`.",
            "example": "urn:cen.eu:en16931:2017"
          },
          "processId": {
            "type": "string",
            "description": "Optional BIS 3.0 process ID filter."
          }
        }
      },
      "CapabilitiesResponse": {
        "type": "object",
        "properties": {
          "found": {
            "type": "boolean"
          },
          "accepts": {
            "type": "boolean",
            "description": "Receiver accepts the probed documentType (or `true` when no filter supplied). Always `false` when `found=false`."
          },
          "reason": {
            "type": "string",
            "description": "Populated when found=false.",
            "example": "Participant not registered in Peppol network"
          },
          "participant": {
            "type": "object",
            "properties": {
              "scheme": {
                "type": "string"
              },
              "identifier": {
                "type": "string"
              },
              "id": {
                "type": "string",
                "example": "0245:2122701339"
              }
            }
          },
          "accessPoint": {
            "type": "object",
            "nullable": true,
            "description": "Receiver's Peppol AP endpoint. null when found=false.",
            "properties": {
              "url": {
                "type": "string",
                "example": "https://ap.epostak.sk/as4"
              },
              "transportProfile": {
                "type": "string",
                "example": "peppol-transport-as4-v2_0"
              }
            }
          },
          "internal": {
            "type": "boolean",
            "description": "Receiver is hosted on this AP"
          },
          "supportedDocumentTypes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "matchedDocumentType": {
            "type": "string",
            "nullable": true,
            "description": "Tri-state: the matched doctype string, or `null` when no filter was supplied, or `null` with `accepts:false` when filter didn't match."
          },
          "source": {
            "type": "string",
            "nullable": true,
            "description": "Lookup cache origin (e.g. `sml`, `cache`, `internal`)"
          }
        }
      },
      "ParticipantsBatchRequest": {
        "type": "object",
        "required": [
          "participants"
        ],
        "properties": {
          "participants": {
            "type": "array",
            "minItems": 1,
            "maxItems": 100,
            "items": {
              "type": "object",
              "required": [
                "scheme",
                "identifier"
              ],
              "properties": {
                "scheme": {
                  "type": "string",
                  "example": "0245"
                },
                "identifier": {
                  "type": "string",
                  "example": "12345678"
                }
              }
            }
          }
        }
      },
      "ParticipantsBatchResponse": {
        "type": "object",
        "properties": {
          "total": {
            "type": "integer",
            "example": 42
          },
          "found": {
            "type": "integer",
            "example": 38
          },
          "notFound": {
            "type": "integer",
            "example": 4
          },
          "results": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "index": {
                  "type": "integer"
                },
                "participant": {
                  "type": "object",
                  "properties": {
                    "scheme": {
                      "type": "string"
                    },
                    "identifier": {
                      "type": "string"
                    },
                    "id": {
                      "type": "string"
                    }
                  }
                },
                "found": {
                  "type": "boolean"
                },
                "accessPoint": {
                  "type": "object",
                  "nullable": true,
                  "description": "Receiver's Peppol AP endpoint. null when found=false.",
                  "properties": {
                    "url": {
                      "type": "string",
                      "example": "https://ap.epostak.sk/as4"
                    },
                    "transportProfile": {
                      "type": "string",
                      "example": "peppol-transport-as4-v2_0"
                    }
                  }
                },
                "internal": {
                  "type": "boolean"
                },
                "supportedDocumentTypes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "nullable": true
                },
                "source": {
                  "type": "string",
                  "nullable": true
                },
                "error": {
                  "type": "string",
                  "nullable": true,
                  "description": "Invalid scheme/identifier format (per-item)"
                }
              }
            }
          }
        }
      },
      "WebhookTestResponse": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean"
          },
          "statusCode": {
            "type": "integer",
            "nullable": true,
            "example": 200
          },
          "responseTime": {
            "type": "integer",
            "description": "Elapsed milliseconds end-to-end.",
            "example": 142
          },
          "webhookId": {
            "type": "string",
            "description": "Synthetic `whk_test_*` ID used in the delivery headers."
          },
          "event": {
            "type": "string",
            "example": "document.created"
          },
          "error": {
            "type": "string",
            "nullable": true,
            "description": "Populated when success=false."
          }
        }
      },
      "WebhookDeliveryDetail": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "webhookId": {
            "type": "string"
          },
          "event": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "PENDING",
              "SUCCESS",
              "FAILED",
              "RETRYING"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "responseStatus": {
            "type": "integer",
            "nullable": true
          },
          "responseBody": {
            "type": "string",
            "nullable": true,
            "description": "First 200 chars of the receiver's response body (or error message). **Only present when the request included `?includeResponseBody=true` or `?include=responseBody`.** Omitted by default to avoid leaking content from receivers that echo sensitive data."
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "nextRetryAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "DeliveriesResponse": {
        "type": "object",
        "properties": {
          "deliveries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDeliveryDetail"
            }
          },
          "total": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        }
      },
      "WebhookRotateSecretResponse": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string"
          },
          "secret": {
            "type": "string",
            "description": "New 64-char hex HMAC secret. Shown once.",
            "example": "a3f8...hex64chars"
          },
          "message": {
            "type": "string",
            "example": "Secret rotated. Save it — it will not be shown again. The previous secret is now invalid."
          }
        }
      },
      "ValidateResponse": {
        "type": "object",
        "description": "3-layer validation report. Structure mirrors the internal `validateUBLXml()` return type.",
        "properties": {
          "valid": {
            "type": "boolean"
          },
          "layers": {
            "type": "object",
            "properties": {
              "xsd": {
                "type": "object",
                "properties": {
                  "passed": {
                    "type": "boolean"
                  },
                  "errors": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "en16931": {
                "type": "object",
                "properties": {
                  "passed": {
                    "type": "boolean"
                  },
                  "errors": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "warnings": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "peppol": {
                "type": "object",
                "properties": {
                  "passed": {
                    "type": "boolean"
                  },
                  "errors": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "warnings": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "profile": {
            "type": "string",
            "nullable": true,
            "description": "Detected BIS profile (Billing, SelfBilling, ...)"
          }
        }
      }
    }
  }
}
