{
  "openapi": "3.1.0",
  "info": {
    "title": "NaprawKSeF API",
    "version": "1.2.0",
    "description": "REST API dla walidacji KSeF i generatora korekt FA(3). Autentykacja przez Bearer API key.\n\nFormat klucza: `nk_(live|test)_<36 hex chars>`. Klucze tworzysz w panelu /dashboard/api-keys.\n\nWszystkie endpointy zwracają JSON ze standardową strukturą:\n- success: `{ data: ..., meta: { request_id, key_prefix } }`\n- error:   `{ error: { code, message, details? }, meta: { request_id } }`\n\nNagłówki:\n- `X-Request-ID` - zawsze (na success + error)\n- `X-RateLimit-Limit-Minute / -Day`, `-Used-*`, `-Remaining-*`, `-Reset-*` - limity klucza API\n- `X-Quota-Day-* / -Month-*` - limity planu (na endpointach które konsumują quota)\n- `Retry-After` - gdy 429\n\nIdempotency: POST endpointy (validate, correction/build, webhooks) wspierają nagłówek `Idempotency-Key` (8-200 znaków, [A-Za-z0-9_-]). Powtórne wywołanie z tym samym kluczem i body zwraca poprzednią odpowiedź z `Idempotency-Replay: true`. Powtórzenie z innym body zwraca 409.\n\nWebhooks: subskrybuj zdarzenia (`validation.completed`, `correction.created`, `api_key.*`, `quota.exceeded`) przez `/v1/webhooks`. Każde żądanie ma podpis HMAC-SHA256 w nagłówku `X-NK-Signature: t=<unix>,v1=<hex>`. Weryfikacja: `v1 === hmac_sha256(secret, t + \".\" + body)` w constant time, tolerancja 5 min.",
    "contact": {
      "name": "NaprawKSeF Support",
      "email": "support@naprawksef.pl",
      "url": "https://naprawksef.pl"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://naprawksef.pl/regulamin"
    }
  },
  "servers": [
    {
      "url": "https://naprawksef.pl/api/v1",
      "description": "Production"
    }
  ],
  "security": [{ "bearerAuth": [] }],
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "nk_(live|test)_<hex>",
        "description": "API key w formacie `Authorization: Bearer nk_live_<klucz>`"
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "description": "Idempotency token (8-200 znaków, [A-Za-z0-9_-]). Powtórne wywołanie z tym samym kluczem i identycznym body zwróci poprzednią odpowiedź.",
        "required": false,
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 200,
          "pattern": "^[A-Za-z0-9_-]+$"
        },
        "example": "client-req-2026-05-22-001"
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "required": ["request_id"],
        "properties": {
          "request_id": {
            "type": "string",
            "description": "Unikalny ID requestu (16 hex chars).",
            "example": "a3f9c1d8b2e7f4a1"
          },
          "key_prefix": {
            "type": "string",
            "example": "nk_live_a3f9"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "meta"],
        "properties": {
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string", "example": "INVALID_KEY" },
              "message": { "type": "string" },
              "details": {}
            }
          },
          "meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "ValidationIssue": {
        "type": "object",
        "properties": {
          "severity": {
            "type": "string",
            "enum": ["critical", "error", "warning", "info"]
          },
          "code": { "type": "string", "example": "XML-001" },
          "message": { "type": "string" },
          "field_name": { "type": ["string", "null"] },
          "field_value": { "type": ["string", "null"] },
          "expected_value": { "type": ["string", "null"] },
          "xpath": { "type": ["string", "null"] },
          "line": { "type": ["integer", "null"] },
          "column": { "type": ["integer", "null"] },
          "quick_fix": { "type": ["string", "null"] },
          "help_url": { "type": ["string", "null"] },
          "is_auto_fixable": { "type": "boolean" },
          "can_actually_auto_fix": { "type": "boolean" }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "validation.completed",
          "correction.created",
          "api_key.created",
          "api_key.revoked",
          "quota.exceeded",
          "partner.trial_started",
          "partner.trial_ending_soon",
          "partner.trial_ended",
          "platform.incident_created",
          "platform.incident_updated",
          "platform.incident_resolved",
          "webhook.test"
        ]
      },
      "ErpPreset": {
        "type": "object",
        "required": ["slug", "name", "vendor", "version", "vat_rate_map", "jpk_field_aliases"],
        "properties": {
          "slug": { "type": "string", "example": "trawers-v7" },
          "name": { "type": "string" },
          "vendor": { "type": "string" },
          "version": { "type": "string" },
          "description": { "type": "string" },
          "documentation_url": { "type": ["string", "null"], "format": "uri" },
          "vat_rate_map": {
            "type": "object",
            "additionalProperties": { "type": "string" },
            "description": "Mapping ERP internal VAT code → KSeF FA(3) P_12 literal."
          },
          "jpk_field_aliases": {
            "type": "object",
            "additionalProperties": { "type": "string" },
            "description": "Mapping ERP internal code → JPK_V7M field (K_XX)."
          },
          "scenario_overrides": {
            "type": "object",
            "description": "Per-scenario overrides (method, allowed pairs, etc)."
          },
          "default_settings": { "type": "object" },
          "is_official": { "type": "boolean" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": ["id", "name", "url", "events", "is_active", "secret_prefix"],
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "name": { "type": "string" },
          "url": { "type": "string", "format": "uri" },
          "description": { "type": ["string", "null"] },
          "events": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/WebhookEventType" },
                { "type": "string", "enum": ["*"] }
              ]
            }
          },
          "is_active": { "type": "boolean" },
          "auto_disabled_at": { "type": ["string", "null"], "format": "date-time" },
          "secret_prefix": { "type": "string", "example": "whsec_a3f9" },
          "consecutive_failures": { "type": "integer" },
          "total_deliveries": { "type": "integer" },
          "total_failures": { "type": "integer" },
          "last_success_at": { "type": ["string", "null"], "format": "date-time" },
          "last_failure_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" },
          "updated_at": { "type": "string", "format": "date-time" }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "properties": {
          "id": { "type": "string", "format": "uuid" },
          "event_id": { "type": "string", "format": "uuid" },
          "status": {
            "type": "string",
            "enum": ["pending", "in_flight", "succeeded", "failed", "dead_letter"]
          },
          "attempt": { "type": "integer" },
          "max_attempts": { "type": "integer" },
          "next_retry_at": { "type": ["string", "null"], "format": "date-time" },
          "last_http_status": { "type": ["integer", "null"] },
          "last_duration_ms": { "type": ["integer", "null"] },
          "last_error": { "type": ["string", "null"] },
          "response_excerpt": { "type": ["string", "null"] },
          "completed_at": { "type": ["string", "null"], "format": "date-time" },
          "created_at": { "type": "string", "format": "date-time" }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Brak / nieprawidłowy klucz API.",
        "headers": {
          "X-Request-ID": { "schema": { "type": "string" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "INVALID_KEY",
                "message": "Klucz API jest nieprawidłowy lub został unieważniony."
              },
              "meta": { "request_id": "a3f9c1d8b2e7f4a1" }
            }
          }
        }
      },
      "Forbidden": {
        "description": "Klucz nie ma wymaganego scope.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "QuotaExceeded": {
        "description": "Limit planu wyczerpany (Retry-After w nagłówku).",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" } },
          "X-Quota-Day-Limit": { "schema": { "type": "integer" } },
          "X-Quota-Day-Used": { "schema": { "type": "integer" } },
          "X-Quota-Day-Remaining": { "schema": { "type": "integer" } },
          "X-Quota-Month-Limit": { "schema": { "type": "integer" } },
          "X-Quota-Month-Used": { "schema": { "type": "integer" } },
          "X-Quota-Month-Remaining": { "schema": { "type": "integer" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "RateLimitExceeded": {
        "description": "Limit klucza API wyczerpany (Retry-After w nagłówku).",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" } },
          "X-RateLimit-Limit-Minute": { "schema": { "type": "integer" } },
          "X-RateLimit-Used-Minute": { "schema": { "type": "integer" } },
          "X-RateLimit-Remaining-Minute": { "schema": { "type": "integer" } },
          "X-RateLimit-Reset-Minute": { "schema": { "type": "integer" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" }
          }
        }
      },
      "IdempotencyConflict": {
        "description": "Idempotency-Key użyty z innym body lub powtórzony przed zakończeniem poprzedniego żądania.",
        "headers": {
          "Retry-After": { "schema": { "type": "integer" } }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": {
              "error": {
                "code": "IDEMPOTENCY_CONFLICT",
                "message": "Ten Idempotency-Key został już użyty z innym body żądania."
              },
              "meta": { "request_id": "..." }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/health": {
      "get": {
        "summary": "Liveness check",
        "description": "Public smoke test - bez auth. Zwraca status serwisu, wersję, timestamp.",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "status": "ok",
                    "api_version": "1.0.0",
                    "timestamp": "2026-05-22T12:34:56.789Z",
                    "environment": "production"
                  },
                  "meta": { "request_id": "a3f9c1d8b2e7f4a1" }
                }
              }
            }
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Whoami",
        "description": "Pobiera info o aktualnym kluczu, organizacji i bieżącym oknie rate-limit.",
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit-Minute": { "schema": { "type": "integer" } },
              "X-RateLimit-Remaining-Minute": { "schema": { "type": "integer" } }
            },
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "key": {
                      "id": "abc-123",
                      "prefix": "nk_live_a3f9",
                      "name": "Production integration",
                      "environment": "live",
                      "scopes": ["validate", "correction", "webhooks"],
                      "expires_at": null
                    },
                    "organization": { "id": "org-uuid", "name": "Trawers Sp. z o.o.", "plan": "api" },
                    "limits": { "rate_limit_per_minute": 600, "rate_limit_per_day": 200000 },
                    "rate_limit": {
                      "windows": [
                        { "window": "minute", "limit": 600, "used": 12, "remaining": 588, "reset_at": 1715000060 },
                        { "window": "day", "limit": 200000, "used": 1450, "remaining": 198550, "reset_at": 1715126400 }
                      ]
                    }
                  },
                  "meta": { "request_id": "..." }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/scenarios": {
      "get": {
        "summary": "Katalog scenariuszy korekt",
        "description": "Zwraca wszystkie 12 scenariuszy korekt FA(3). Read-only.",
        "responses": {
          "200": { "description": "OK" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/validate": {
      "post": {
        "summary": "Walidacja XML KSeF",
        "description": "Sprawdza XML faktury KSeF pod kątem schematu + business rules. Quota per plan. Sandbox key nie konsumuje quota. Emituje webhook `validation.completed`.",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["xml"],
                "properties": {
                  "xml": { "type": "string" },
                  "filename": { "type": "string" }
                }
              }
            },
            "application/xml": {
              "schema": { "type": "string", "format": "xml" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-RateLimit-Limit-Minute": { "schema": { "type": "integer" } },
              "X-Quota-Day-Remaining": { "schema": { "type": "integer" } },
              "Idempotency-Replay": { "schema": { "type": "string", "enum": ["true"] } }
            }
          },
          "400": { "description": "Bad request - invalid XML/JSON, file too large." },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/correction/analyze": {
      "post": {
        "summary": "Analiza pierwotnej faktury (read-only)",
        "description": "Rozpoznaje metadane + sugeruje 5 najtrafniejszych scenariuszy korekty. Nie konsumuje quota.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["xml"],
                "properties": { "xml": { "type": "string" } }
              }
            },
            "application/xml": { "schema": { "type": "string" } }
          }
        },
        "responses": {
          "200": { "description": "OK" },
          "400": { "description": "Bad request" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "429": { "$ref": "#/components/responses/RateLimitExceeded" }
        }
      }
    },
    "/correction/build": {
      "post": {
        "summary": "Generuj XML korekty FA(3)",
        "description": "Tworzy XML faktury korygującej z pierwotnej + listy zmian. Quota per plan. Emituje webhook `correction.created`.",
        "parameters": [
          { "$ref": "#/components/parameters/IdempotencyKey" }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["source_xml", "scenario_id", "changes", "przyczyna_korekty", "numer_faktury_kor"],
                "properties": {
                  "source_xml": { "type": "string" },
                  "scenario_id": { "type": "string" },
                  "changes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": ["path", "new_value"],
                      "properties": {
                        "path": { "type": "string" },
                        "new_value": { "type": "string" },
                        "description": { "type": "string" }
                      }
                    }
                  },
                  "przyczyna_korekty": { "type": "string" },
                  "numer_faktury_kor": { "type": "string" },
                  "desired_jpk_field": { "type": "string", "pattern": "^K_\\d{1,3}$" },
                  "question_answers": { "type": "object", "additionalProperties": { "type": "string" } },
                  "data_wystawienia": { "type": "string", "format": "date" },
                  "force_typ_korekty": { "type": "string", "enum": ["A", "B", "C"] },
                  "force_rodzaj_faktury": { "type": "string", "enum": ["KOR", "KOR_ZAL", "KOR_ROZ"] },
                  "preset": {
                    "type": "string",
                    "pattern": "^[a-z0-9-]+$",
                    "description": "Optional ERP preset slug (e.g. `trawers-v7`, `comarch-optima`). Translates `preset:vat:*` and `preset:jpk:*` change values."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "Idempotency-Replay": { "schema": { "type": "string", "enum": ["true"] } }
            }
          },
          "400": { "description": "Bad request" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "409": { "$ref": "#/components/responses/IdempotencyConflict" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/correction/audit-pack": {
      "post": {
        "summary": "Paczka audytowa korekty (ZIP)",
        "description": "Generuje ZIP z faktura-pierwotna.xml, korekta.xml, raport-audytowy.pdf (server-side render), walidacja.json i manifest.json (SHA-256). Wymaga scope `correction`. Nie konsumuje quota - operuje na już wygenerowanej korekcie.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["source_xml", "correction_xml", "scenario_id", "przyczyna_korekty", "numer_faktury_kor"],
                "properties": {
                  "source_xml": { "type": "string" },
                  "correction_xml": { "type": "string" },
                  "scenario_id": { "type": "string" },
                  "przyczyna_korekty": { "type": "string" },
                  "numer_faktury_kor": { "type": "string" },
                  "preset": { "type": "string", "pattern": "^[a-z0-9-]+$" },
                  "summary": {
                    "type": "object",
                    "properties": {
                      "rodzaj_faktury": { "type": "string" },
                      "typ_korekty": { "type": "string" },
                      "net_delta": { "type": "number" },
                      "vat_delta": { "type": "number" },
                      "gross_delta": { "type": "number" }
                    }
                  },
                  "validation": { "type": "array", "items": { "type": "object" } },
                  "warnings": { "type": "array", "items": { "type": "string" } }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "application/zip (audit-pack-<numer>.zip)",
            "content": {
              "application/zip": {
                "schema": { "type": "string", "format": "binary" }
              }
            },
            "headers": {
              "Content-Disposition": { "schema": { "type": "string" } },
              "X-Audit-Pack-Schema": { "schema": { "type": "string", "example": "naprawksef.audit-pack/v1" } }
            }
          },
          "400": { "description": "Bad request - invalid XML or missing field" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "500": { "description": "PDF render failed" }
        }
      }
    },
    "/erp-presets": {
      "get": {
        "summary": "Katalog presetów ERP",
        "description": "Zwraca listę aktywnych presetów ERP - vat_rate_map, jpk_field_aliases, scenario_overrides. Wymaga scope `scenarios` lub `correction`. Cache po stronie serwera 5 minut.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "presets": {
                          "type": "array",
                          "items": { "$ref": "#/components/schemas/ErpPreset" }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/webhooks": {
      "get": {
        "summary": "Lista webhooków organizacji",
        "description": "Wymaga scope `webhooks`. Zwraca także listę wspieranych typów zdarzeń + format podpisu.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "webhooks": [{ "id": "...", "name": "ERP prod", "url": "https://...", "events": ["*"], "is_active": true, "secret_prefix": "whsec_a3f9" }],
                    "supported_events": ["validation.completed", "correction.created"],
                    "signature_header": "X-NK-Signature",
                    "signature_format": "t=<unix-seconds>,v1=<hex-hmac-sha256(secret, t + \".\" + body)>",
                    "signature_tolerance_seconds": 300
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      },
      "post": {
        "summary": "Utwórz webhook (signing secret zwracany RAZ)",
        "description": "Wymaga scope `webhooks`. URLe localhost/prywatne są odrzucane (SSRF guard). Otrzymany sekret musisz zachować - nie będzie ponownie dostępny.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["name", "url"],
                "properties": {
                  "name": { "type": "string", "maxLength": 120 },
                  "url": { "type": "string", "format": "uri" },
                  "description": { "type": "string", "maxLength": 500 },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "items": { "type": "string" },
                    "default": ["*"]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Utworzony",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "webhook": { "id": "...", "name": "ERP prod", "url": "https://...", "events": ["*"], "is_active": true, "secret_prefix": "whsec_a3f9" },
                    "secret": "whsec_a3f9...",
                    "warning": "Ten secret zobaczysz TYLKO RAZ. Zapisz go.",
                    "signature_format": "t=<unix-seconds>,v1=hex(hmac_sha256(secret, t + \".\" + body))"
                  }
                }
              }
            }
          },
          "400": { "description": "Bad request - invalid URL, unknown event, SSRF" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" }
        }
      }
    },
    "/webhooks/{id}": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
      ],
      "get": {
        "summary": "Szczegóły webhooka",
        "responses": {
          "200": { "description": "OK" },
          "404": { "description": "Nie istnieje" }
        }
      },
      "patch": {
        "summary": "Edytuj webhook",
        "description": "Częściowa aktualizacja. Pole `is_active: true` reaktywuje auto-wyłączony endpoint i zeruje `consecutive_failures`.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": { "type": "string", "maxLength": 120 },
                  "url": { "type": "string", "format": "uri" },
                  "description": { "type": ["string", "null"], "maxLength": 500 },
                  "events": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
                  "is_active": { "type": "boolean" }
                }
              }
            }
          }
        },
        "responses": {
          "200": { "description": "OK" },
          "404": { "description": "Nie istnieje" }
        }
      },
      "delete": {
        "summary": "Usuń webhook (i całą historię wysyłek)",
        "responses": {
          "200": { "description": "Usunięty" },
          "404": { "description": "Nie istnieje" }
        }
      }
    },
    "/webhooks/{id}/deliveries": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } },
        { "name": "limit", "in": "query", "required": false, "schema": { "type": "integer", "minimum": 1, "maximum": 200, "default": 50 } },
        { "name": "status", "in": "query", "required": false, "schema": { "type": "string", "enum": ["pending", "in_flight", "succeeded", "failed", "dead_letter"] } }
      ],
      "get": {
        "summary": "Historia wysyłek do webhooka",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "endpoint": { "$ref": "#/components/schemas/WebhookEndpoint" },
                        "deliveries": {
                          "type": "array",
                          "items": { "$ref": "#/components/schemas/WebhookDelivery" }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/webhooks/{id}/test": {
      "parameters": [
        { "name": "id", "in": "path", "required": true, "schema": { "type": "string", "format": "uuid" } }
      ],
      "post": {
        "summary": "Wyślij testowy event do endpointa",
        "description": "Emituje syntetyczny `webhook.test` event i synchronously POST-uje go do URLa subskrybenta. Dobre do weryfikacji implementacji podpisu.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "example": {
                  "data": {
                    "attempted": true,
                    "succeeded": true,
                    "http_status": 200,
                    "duration_ms": 142,
                    "network_error": null,
                    "response_excerpt": "{\"ok\":true}",
                    "signature_sent": { "header": "t=...,v1=...", "timestamp": 1715000000, "secret_prefix": "whsec_a3f9" },
                    "envelope": {
                      "id": "test_lk1abc",
                      "type": "webhook.test",
                      "created_at": "2026-05-22T12:34:56.789Z",
                      "api_version": "v1",
                      "organization_id": "...",
                      "data": { "message": "Test event ...", "triggered_by_user_id": "..." }
                    }
                  }
                }
              }
            }
          },
          "404": { "description": "Webhook nie istnieje" },
          "409": { "description": "Webhook jest wyłączony - włącz przed wysłaniem testu" }
        }
      }
    }
  }
}
