{
  "openapi": "3.0.3",
  "info": {
    "title": "ProofSnap API",
    "version": "2.0.0",
    "license": {
      "name": "Proprietary",
      "url": "https://getproofsnap.com/terms.html"
    },
    "description": "ProofSnap API wczytuje stronę internetową w przeglądarce Chromium po stronie serwera i zwraca pakiet dowodowy (ZIP).\n\nKażdy pakiet przechwycenia zawiera zrzut ekranu, kod HTML strony, wyodrębniony tekst strony, metadata.json, forensic_log.json, chain_of_custody.json, evidence.pdf oraz manifest.json, który zawiera skrót SHA-256 każdego pliku. manifest.json jest podpisywany kluczem RSA-4096 wygenerowanym dla danego przechwycenia (klucz publiczny jest dołączany jako publickey.pem, klucz prywatny jest odrzucany), a skrót SHA-256 pliku manifest.json jest przesyłany do kalendarzy OpenTimestamps w celu zakotwiczenia w Bitcoinie (manifest.json.ots). Jeśli żądanie do kalendarza się nie powiedzie, pakiet jest dostarczany bez dowodu, a manifest.json go nie wymienia, więc skrypty offline i weryfikator zgłaszają go jako nieobecny, a nie brakujący. Z `include_eidas` przy produkcyjnym kluczu API ten sam skrót otrzymuje także kwalifikowany elektroniczny znacznik czasu eIDAS (RFC 3161, manifest.json.tsr). Zrzuty ekranu JPEG są w miarę możliwości podpisywane za pomocą C2PA Content Credentials: gdy podpisanie się nie powiedzie, przechwycenie kończy się bez nich. Folder verification/ zawiera skrypty, które sprawdzają pakiet offline. Każdy manifest.json podaje `ots_listed_only_when_present: true` (manifest.json.ots jest wymieniony tylko wtedy, gdy znajduje się w pakiecie) oraz `eidas_marker_version: 1`. Zapisuje `eidas_requested: true` tylko wtedy, gdy pakiet jest dostarczany z kwalifikowanym znacznikiem czasu. Jeśli tokenu nie można uzyskać, manifest jest pieczętowany ponownie bez niego. Pakiet, który zapisuje `eidas_requested: true`, ale nie zawiera manifest.json.tsr, utracił więc swój token, a skrypty offline zgłaszają FAIL. chain_of_custody.json zapisuje `bitcoin_ots_included` i wymienia w `page_modifications.items[]` każdą zmianę wprowadzoną na stronie przed przechwyceniem. manifest.json zawiera sealed_at (kiedy został zbudowany, po obliczeniu skrótów wszystkich plików) oraz przesunięcia zegara serwera przechwytującego zmierzone przez SNTP; chain_of_custody.json wymienia kroki przechwycenia w kolejności, w jakiej nastąpiły, każdy z czasem zakończenia, i kończy się krokiem custody_record_written (obliczanie skrótów, podpis i znaczniki czasu następują po nim i poświadczają je manifest.json oraz dowody).\n\nmetadata.json zapisuje:\n\n- `browser_identity`: faktycznie wysłany user agent i Client Hints.\n- `dns`: niezależne sprawdzenie DNS adresu, z którym połączyła się przeglądarka, z odpowiedziami dns.google i cloudflare-dns.com oraz wynikiem: confirmed, not_confirmed, resolvers_unavailable, connected_ip_unknown, skipped_ip_literal (URL zawiera adres IP, więc nie ma nazwy hosta do wyszukania) lub skipped_internal_host (wewnętrzna nazwa hosta, która nigdy nie jest wysyłana do publicznych resolwerów).\n- `url_at_load` i `url_at_screenshot`: adres tuż po wczytaniu strony i w chwili wykonania jej obrazu (strona może sama zmienić swój adres, najczęściej część po #).\n- `redirect_chain`: przekierowania głównego dokumentu, od najstarszego, każde z kodem statusu HTTP. Adres żądany i końcowy są podane w całości; w każdym pośrednim przeskoku wartości zapytania są zastąpione przez [redacted] (`query_values_redacted`).\n- `tls.source`: skąd pochodzi każde pole TLS, `browser_connection` albo `browser_connection_and_separate_handshake`, w tym drugim przypadku z `at`, `ip` i `port` osobnego uzgadniania (handshake), które po wczytaniu strony pobrało certificate_chain.pem.\n\nGdy żądany krok nie został wykonany (znacznik czasu eIDAS, podpis C2PA, żądanie do archiwum, WHOIS, drugi odczyt zegara, WARC obcięty przez limit czasu albo oczekiwanie lub rozwinięcie treści pominięte z powodu limitu czasu), evidence.pdf wymienia go z krótkim powodem, a certyfikat pochodzenia wspomina o nim w jednym wierszu.\n\nPrzechwycenia są asynchroniczne. POST /captures zwraca 202 z capture_id; odpytuj GET /captures/{capture_id} lub przekaż `options.webhook_url`. Ukończone przechwycenie zwraca wstępnie podpisany package_url ważny do 24 godzin (zob. package_url_expires_at); aby uzyskać świeży link, odczytaj przechwycenie ponownie.\n\nPoniższe ścieżki są względne wobec URL serwera, który zawiera już etap /v1. Wartości `poll_path` zwracane przez API zawierają /v1.",
    "contact": {
      "email": "support@getproofsnap.com"
    }
  },
  "servers": [
    {
      "url": "https://api.getproofsnap.com/v1",
      "description": "ProofSnap API (EU)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Przechwycenia",
      "description": "Tworzenie, wyświetlanie listy, odczyt, usuwanie i weryfikacja przechwyceń.\n\n**Limity liczby żądań.** `POST /captures`, `POST /captures/batch`, `POST /compare`, `POST /captures/{capture_id}/redact` i `POST /certify-hash` mają limit liczby żądań na konto w stałych 60-sekundowych oknach: wszystkie klucze API konta i jego sesja w panelu współdzielą jedno okno (dodanie kluczy nie podnosi limitu). Partia lub certyfikacja wielu skrótów zużywa jedną jednostkę na każdy URL lub skrót. Domyślne limity na okno według planu:\n\n| Plan | Żądań na 60 s |\n| --- | --- |\n| sandbox | 10 |\n| developer | 60 |\n| startup | 120 |\n| growth | 300 |\n| custom | 1200 |\n| legal_solo | 60 |\n| legal_seats | 120 |\n| website_archive | 60 |\n| archive_only | 30 |\n| każdy inny plan | 30 |\n\nZ tego samego okna korzystają również poniższe żądania, po jednej jednostce na żądanie, o ile nie podano inaczej: `POST /captures/{capture_id}/verify`, `PATCH /captures/{capture_id}` z akcją legal_hold, `GET /captures/hosts` (tylko gdy lista jest obliczana od nowa, a nie zwracana z pamięci podręcznej), `POST /legal-holds` (jedna jednostka na 10 przechwyceń), `POST /captures/export-jobs` oraz `POST /imports`, `POST /imports/{import_id}/complete` i `GET /imports/{import_id}?upload=true`.\n\nPo przekroczeniu limitu API zwraca 429 RATE_LIMITED z nagłówkami `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` i `X-RateLimit-Reset` oraz polem `error.details.retry_after_seconds`. Żądanie, które wymaga więcej jednostek, niż pozwala jedno okno (certyfikacja wielu skrótów większa niż limit na okno), nigdy nie może się powieść i zwraca 400 RATE_LIMIT_COST_TOO_HIGH z `error.details` `{ max_per_window, window_seconds, requested }`. Podziel je na mniejsze żądania. Odrzucone żądanie nie zużywa jednostek, a nagłówki podają faktyczny pozostały budżet.\n\n**Limity miesięczne.** Przechwycenia, porównania, zaczernienia i certyfikacje wykonane produkcyjnymi kluczami API wliczają się do miesięcznego maksimum na konto (miesiąc kalendarzowy UTC). Przyjęta odpowiedź zawiera nagłówki `X-Quota-*`. Osiągnięcie maksimum zwraca 402 QUOTA_EXCEEDED z `error.details` `{ reason, used, limit, max_limit, cap, org_member }`, gdzie `cap` ma wartość `plan_max`, `customer`, `member_owner` lub `org`. Gdy produkcyjna subskrypcja jest zaległa lub nieopłacona, żądania podlegające rozliczeniu zwracają 402 PAYMENT_REQUIRED; dla takiego konta przechwycenia (pojedyncze i wsadowe) są odrzucane z tym błędem niezależnie od środowiska klucza, także dla testowych kluczy API.\n\n**Równoczesne przechwycenia i tryb testowy.** Każde konto może jednocześnie wykonywać tylko ograniczoną liczbę przechwyceń (developer 10, startup 25, growth 100, legal_solo i legal_seats 25, custom 200, website_archive 25, sandbox 5). Powyżej tego limitu API zwraca 429 CONCURRENT_CAPTURE_LIMIT; partia uruchamia te adresy URL, które się mieszczą, a pozostałe wymienia w `not_admitted` (każdy z `code` CONCURRENT_CAPTURE_LIMIT oraz `in_flight_limit`), więc wyślij je ponownie później. Praca w trybie testowym (testowy klucz API lub sesja bez powiązanego rozliczenia) jest ograniczona do 200 jednostek na konto na dobę UTC (429 TEST_MODE_DAILY_LIMIT; nagłówki `X-Test-Daily-*`), a przechwycenie testowe nigdy nie jest wysyłane do Internet Archive (`external_archive.skip_reason` \"test_mode\")."
    },
    {
      "name": "Porównanie i zaczernianie",
      "description": "Utwórz nowy zapieczętowany pakiet z istniejących przechwyceń: porównanie dwóch przechwyceń lub zaczernioną kopię jednego."
    },
    {
      "name": "Certyfikacja skrótu",
      "description": "Certyfikuj skróty SHA-256 plików, które przechowujesz. Wysyłane są tylko skróty; pliki nigdy nie trafiają do ProofSnap."
    },
    {
      "name": "Automatyczne przechwycenia",
      "description": "Cykliczne przechwycenia (interval lub cron) i monitorowanie zmian (on_change). Każde uruchomienie, które wykonuje przechwycenie, jest zwykłym przechwyceniem: jest naliczane i wlicza się do miesięcznych limitów."
    },
    {
      "name": "Sprawy",
      "description": "Obszary robocze spraw prawnych z dziennikiem łańcucha dowodowego połączonym w łańcuch skrótów. Dostępne w planach Legal (legal_solo, legal_seats); pozostałe plany otrzymują 403 MATTERS_NOT_ENABLED, z dwoma wyjątkami. Właściciel firmy, której subskrypcja się zakończyła, może nadal wyświetlać listę spraw tej firmy, otwierać je i eksportować aż do daty usunięcia danych firmy (data_delete_after); ten dostęp obejmuje tylko odczyt i eksport, a GET /matters dodaje wtedy read_only: true. Konto w planie Archive only może wyświetlać listę swoich spraw, otwierać je i eksportować. Tylko właściciel sprawy może zmienić jej nazwę, zarchiwizować ją, zarządzać jej członkami, zapieczętować ją, udostępnić lub wyeksportować. Właścicielem sprawy jest jej twórca. W przypadku sprawy firmowej właściciel firmy staje się właścicielem sprawy dopiero po odejściu twórcy z firmy. Właściciel firmy dodany jako członek ma wyłącznie uprawnienia członka. Członkowie otrzymują 403 FORBIDDEN dla tych operacji. Każdy zapis w sprawie (utworzenie, PATCH, przypisanie przechwyceń, udostępnienie i jego cofnięcie, zapieczętowanie, eksport) wymaga potwierdzonego adresu e-mail (403 EMAIL_NOT_VERIFIED, zarówno dla sesji panelu, jak i dla klucza API konta, którego adres nie jest potwierdzony), a w przypadku wywołującego produkcyjnego (produkcyjny klucz API lub sesja z powiązanymi rozliczeniami) jest odrzucany z 402 PAYMENT_REQUIRED, dopóki subskrypcja (lub subskrypcja firmy) jest zaległa lub nieopłacona. Zarchiwizowana sprawa odrzuca zmiany nazwy, nowych członków, nowe przechwycenia, udostępnianie i eksport (409 MATTER_ARCHIVED), dopóki nie zostanie ponownie otwarta; wywołujący, który nie może jej ponownie otworzyć (właściciel zakończonej firmy, konto Archive only), nadal może ją wyeksportować."
    },
    {
      "name": "Webhooki",
      "description": "ProofSnap wysyła (POST) zdarzenie JSON na `webhook_url` przekazany wraz z przechwyceniem, certyfikacją, zaczernieniem lub automatycznym przechwyceniem. URL musi używać https, nie może zawierać danych uwierzytelniających (`user:password@`, 400 INVALID_URL) i nie może wskazywać adresu prywatnego, loopback, link-local, carrier-grade NAT, multicast, zarezerwowanego ani adresu metadanych chmury, łącznie z formami IPv6, które zawierają taki adres IPv4. Jest to sprawdzane przy przyjęciu żądania (422 SSRF_BLOCKED) i ponownie przy każdym dostarczeniu: nazwa hosta jest rozwiązywana, każdy adres jest sprawdzany, a dostarczenie łączy się tylko z adresem, który przeszedł kontrolę, więc host, który później zostanie rozwiązany na zablokowany adres, nie zostanie wywołany. Gdy nazwy hosta nie da się rozwiązać, dostarczenie nie jest wysyłane. Na każdej ścieżce pusty webhook_url lub webhook_url złożony wyłącznie z białych znaków jest traktowany jak brak webhooka. /compare nie wysyła webhooków.\n\n**Subskrypcje konta.** Zamiast webhook_url w poszczególnych żądaniach (lub dodatkowo) zasubskrybuj endpoint na zdarzenia konta za pomocą `POST /webhook-subscriptions`: trafia do niego każde pasujące zdarzenie konta, niezależnie od tego, który klucz, automatyczne przechwycenie lub sesja panelu je wywołały. Każda subskrypcja ma własny sekret (zwracany jednorazowo przy jej utworzeniu), określa typy zdarzeń, które chce otrzymywać, i może filtrować według `host` lub `schedule_id`; subskrypcja produkcyjna otrzymuje zdarzenia tylko dla przechwyceń produkcyjnych, a subskrypcja testowa tylko dla przechwyceń testowych. Klucz API wyświetla i zmienia tylko subskrypcje własnego środowiska; sesja panelu widzi wszystkie. Dostarczenia subskrypcji zawierają `subscription_id` na najwyższym poziomie i nie zawierają wstępnie podpisanego linku (package_url, package_url_expires_at, upgraded_proof_url i compare.package_url są usuwane; monitor.change_detected zawiera compare.compare_id): pobierz pakiet przez GET /captures/{capture_id}. Zdarzenia przechwyceń zawierają też `capture_id` na najwyższym poziomie. webhook-id dostarczenia subskrypcji jest wyprowadzany z subskrypcji, przechwycenia (lub automatycznego przechwycenia) i zdarzenia. Obowiązują opisane niżej zasady dostarczania i ponowień. Ponadto odpowiedź 410 natychmiast wyłącza subskrypcję (disabled_reason gone), dostarczenia kończące się błędem przez 5 dni bez żadnego sukcesu (i co najmniej jeden pełny cykl nieudanych ponowień) wyłączają ją (failing), a unieważnienie klucza API, który ją utworzył, wyłącza ją (key_revoked); PATCH ze status \"active\" ponownie ją aktywuje. Każde zdarzenie jest też przechowywane przez 30 dni w `GET /events`.\n\n**Nagłówki**\n\nKażde dostarczenie jest podpisane zgodnie ze specyfikacją [Standard Webhooks](https://www.standardwebhooks.com/), więc może je zweryfikować dowolna oficjalna biblioteka Standard Webhooks:\n\n| Nagłówek | Wartość |\n| --- | --- |\n| `Content-Type` | `application/json` |\n| `webhook-id` | `msg_<delivery id>`: taki sam dla tego samego zdarzenia tego samego przechwycenia, więc zduplikowane dostarczenie ma ten sam identyfikator. Użyj go do deduplikacji: przetwarzaj każdy webhook-id tylko raz. `GET /webhook-events/{capture_id}` zwraca ten sam identyfikator bez prefiksu `msg_` jako `delivery_id` |\n| `webhook-timestamp` | Czas uniksowy w sekundach, w którym wysłano dostarczenie. Jest objęty podpisem |\n| `webhook-signature` | `v1,<base64>`: HMAC-SHA256 z `<webhook-id>.<webhook-timestamp>.<raw body>`. Podczas rotacji sekretu webhooka zawiera dwa wpisy rozdzielone spacją, `v1,<new> v1,<previous>`; zaakceptuj dostarczenie, gdy pasuje którykolwiek wpis |\n| `User-Agent` | `ProofSnap-Webhook/2.0` |\n\n**Weryfikacja dostarczenia.** Weryfikuj na dokładnie tych bajtach, które otrzymałeś, jeszcze przed parsowaniem. Odrzuć dostarczenie, którego webhook-timestamp różni się od Twojego zegara o więcej niż 5 minut (oficjalne biblioteki robią to same), i deduplikuj według webhook-id. Kluczem podpisu jest część sekretu po `whsec_`, zdekodowana z base64. `webhook_secret` to `whsec_`, po którym następuje standardowy base64, czyli forma oczekiwana przez oficjalne biblioteki, więc przekaż go im bez zmian.\n\n```js\n// npm install standardwebhooks\nimport { Webhook } from \"standardwebhooks\";\nconst wh = new Webhook(process.env.PROOFSNAP_WEBHOOK_SECRET); // whsec_... (webhook_secret)\n// rawBody: the request body as received (string or Buffer), headers: the request headers\nconst event = wh.verify(rawBody, headers); // throws on a bad signature or a timestamp outside 5 minutes\n```\n\nBez biblioteki (Node):\n\n```js\nconst key = Buffer.from(secret.slice(\"whsec_\".length), \"base64\");\nconst signed = `${headers[\"webhook-id\"]}.${headers[\"webhook-timestamp\"]}.${rawBody}`;\nconst expected = crypto.createHmac(\"sha256\", key).update(signed).digest(\"base64\");\nconst ok = headers[\"webhook-signature\"].split(\" \").some((part) => {\n  const [version, sig] = part.split(\",\");\n  return version === \"v1\" && sig && sig.length === expected.length\n    && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));\n});\n// and: Math.abs(Date.now() / 1000 - Number(headers[\"webhook-timestamp\"])) <= 300\n```\n\n**Sekret webhooka.** Każdy klucz API ma własny sekret (`whsec_...`). Jest wyświetlany jednorazowo, gdy tworzysz klucz w panelu w sekcji Developers (pole `webhook_secret` odpowiedzi tworzącej klucz). Później nie można go odczytać: lista kluczy nigdy go nie zwraca. Jeśli go zgubisz lub mógł wyciec, wygeneruj nowy w panelu w sekcji Developers (New webhook secret przy kluczu). Nowy sekret również jest wyświetlany tylko raz. **Okres przejściowy przy rotacji:** przez 24 godziny po rotacji (do `previous_secret_expires_at` w odpowiedzi na rotację) każde dostarczenie jest podpisywane zarówno nowym, jak i poprzednim sekretem (dwa wpisy `v1,` w webhook-signature), więc możesz przełączyć swój endpoint na nowy sekret bez utraty dostarczeń. Druga rotacja w trakcie trwającego okresu przejściowego jest odrzucana z 409 WEBHOOK_SECRET_ROTATION_PENDING i `error.details.previous_secret_expires_at` (kiedy możesz ponownie wygenerować sekret), więc jednocześnie nigdy nie są aktywne więcej niż dwa sekrety, a sekret, na którym odbiorcy mogą jeszcze polegać, nigdy nie jest wycofywany przedwcześnie. Jeśli sekret wyciekł, unieważnij klucz i utwórz nowy. Unieważnienie klucza usuwa oba sekrety. Dostarczenia używają sekretów klucza obowiązujących w chwili wysyłki, także w przypadku pracy rozpoczętej przed rotacją. Żądanie z zalogowanej sesji panelu nie ma klucza API, a więc także sekretu webhooka, dlatego `webhook_url` w takim żądaniu jest odrzucany z 400 `WEBHOOK_REQUIRES_API_KEY` (przechwycenia, partie, zaczernienia, certyfikacje skrótów, tworzenie lub edycja automatycznego przechwycenia). Uruchomienia automatycznych przechwyceń używają sekretu klucza, który utworzył automatyczne przechwycenie. Gdy klucz został unieważniony lub jego sekretu nie da się odczytać w chwili dostarczenia, zdarzenie nie jest wysyłane, a próba jest rejestrowana wraz z przyczyną (odczytaj ją przez `GET /webhook-events/{capture_id}`).\n\n**Dostarczanie.** Każda próba ma limit czasu 10 sekund. Przekierowania nigdy nie są wykonywane: odpowiedź 3xx liczy się jako błąd, tak jak odpowiedź spoza 2xx lub przekroczenie limitu czasu. Treść odpowiedzi nie jest odczytywana. **Ponowienia:** dostarczenie, które przekroczy limit czasu, nie może się połączyć lub otrzyma odpowiedź spoza 2xx (z wyjątkiem 410 Gone), jest wysyłane ponownie po około 5 minutach, 30 minutach, 2 godzinach, 8 godzinach i 24 godzinach (próby od 2 do 6, łącznie około 35 godzin), za każdym razem z tym samym `webhook-id` i nowym `webhook-timestamp`. Potem dostarczenie jest porzucane. Ponowione dostarczenie zawiera ten sam ładunek BEZ 24-godzinnego linku do pobrania (`package_url`, `package_url_expires_at`): pobierz pakiet przez GET /captures/{capture_id}. Dostarczenia dotyczące przechwycenia, które w międzyczasie usunięto, są odrzucane. Przejściowy błąd odczytu sekretu podpisu po naszej stronie jest ponawiany w ten sam sposób. W przypadku 410 Gone, przekierowania, zablokowanego adresu oraz brakującego lub unieważnionego klucza ponowień nie ma. Treść ponowienia może się różnić od pierwszej próby (brak linku do pobrania): deduplikuj według webhook-id, a nie według skrótu treści. Odpowiadaj szybko kodem 2xx. Dziennik prób rejestruje ogólną klasę błędu (connection_failed, timeout, blocked_address, redirect_not_followed, http_<status>), nigdy surowy błąd sieciowy. certify.completed i redaction.completed są wysyłane asynchronicznie, krótko po zwróceniu odpowiedzi. Każda próba jest rejestrowana; dziennik odczytasz przez `GET /webhook-events/{capture_id}`.\n\n**Zdarzenia**\n\n`capture.completed`: przechwycenie (pojedyncze, element partii lub uruchomienie automatycznego przechwycenia z interwałem lub cron) zostało zakończone. Subskrypcje webhooków konta otrzymują je również dla przechwycenia bazowego i przechwyceń zmian automatycznego przechwycenia on_change (własny webhook_url automatycznego przechwycenia otrzymuje dla nich monitor.change_detected). `capture` ma te same pola co zakończone przechwycenie w `GET /captures/{capture_id}`, a dodatkowo `timestamps.eidas`, gdy dodano kwalifikowany znacznik czasu, `schedule_id` (null poza automatycznym przechwyceniem), `test_mode` i `completed_at`.\n\n```json\n{ \"event\": \"capture.completed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"completed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"captured_at\": \"...\", \"completed_at\": \"...\",\n    \"package_url\": \"https://...\", \"package_url_expires_at\": \"...\", \"verification_url\": \"https://...\",\n    \"manifest_hash\": \"sha256:...\", \"timestamps\": { \"bitcoin_ots\": { \"status\": \"pending_confirmation\", \"block_height\": null } },\n    \"c2pa\": { \"included\": true, \"signed_screenshot_sha256\": \"sha256:...\" }, \"file_count\": 21, \"size_bytes\": 1234567, \"metadata\": {} } }\n```\n\n`capture.failed`: nie udało się zakończyć przechwycenia. `error.message` to krótka publiczna przyczyna (w przypadku nieoczekiwanego błędu \"The capture could not be completed.\"); wewnętrzne szczegóły błędu nigdy nie są wysyłane. Dotyczy to także uruchomienia, które uległo awarii lub przekroczyło limit czasu: około 30 minut po starcie okresowy przegląd oznacza je jako failed i wtedy wysyłane jest capture.failed (komunikat \"Capture did not finish (timed out).\"). Ten sam przegląd wysyła certify.failed lub redaction.failed dla certyfikacji skrótu lub zaczernienia, które się nie zakończyły. Uruchomienie, które zakończy się po zgłoszeniu go jako failed, nie jest rozliczane i nie wysyła capture.completed.\n\n```json\n{ \"event\": \"capture.failed\",\n  \"capture\": { \"capture_id\": \"cap_...\", \"status\": \"failed\", \"url\": \"https://example.com\", \"schedule_id\": null, \"test_mode\": false, \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CAPTURE_FAILED\", \"message\": \"...\" }, \"metadata\": {} } }\n```\n\n`certify.completed`: certyfikacja skrótu została zakończona. `certificate` ma te same pola co synchroniczna odpowiedź `POST /certify-hash`. Dostarczane asynchronicznie, krótko po zwróceniu odpowiedzi.\n\n`certify.failed`: nie udało się zakończyć certyfikacji skrótu.\n\n```json\n{ \"event\": \"certify.failed\",\n  \"certificate\": { \"certify_id\": \"cert_...\", \"status\": \"failed\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"CERTIFY_FAILED\", \"message\": \"The certification could not be completed. Retry the request.\" } } }\n```\n\n`redaction.completed`: zaczernienie zostało zakończone. `redaction` ma te same pola co odpowiedź `POST /captures/{capture_id}/redact`.\n\n`redaction.failed`: nie udało się zakończyć zaczernienia. `error.code` to kod, który zwróciłoby żądanie (na przykład REDACTION_LEAK_DETECTED), wraz z `details`, jeśli istnieją; zaczernienie oznaczone przez przegląd jako failed zawiera REDACTION_FAILED.\n\n```json\n{ \"event\": \"redaction.failed\",\n  \"redaction\": { \"redaction_id\": \"red_...\", \"status\": \"failed\", \"redacted_from\": \"cap_...\", \"failed_at\": \"...\",\n    \"error\": { \"code\": \"REDACTION_LEAK_DETECTED\", \"message\": \"...\", \"details\": { \"files\": [\"page.html\"], \"rule_indexes\": [0], \"metadata_fields\": [], \"in_capture_url\": false } } } }\n```\n\n`monitor.change_detected`: automatyczne przechwycenie `on_change` wykryło zmianę tekstu strony i wykonało nowe przechwycenie.\n\n```json\n{ \"event\": \"monitor.change_detected\", \"schedule_id\": \"sch_...\",\n  \"before_capture_id\": \"cap_...\", \"after_capture_id\": \"cap_...\", \"url\": \"https://example.com\",\n  \"compare\": { \"package_url\": \"https://...\", \"diff_summary\": { } },\n  \"diff_status\": \"ok\", \"verdict\": \"changed\", \"text_verdict\": \"changed\", \"images_verdict\": \"unchanged\", \"metadata_verdict\": \"unchanged\",\n  \"detected_at\": \"...\", \"test_mode\": false }\n```\n\n`verdict`, `text_verdict`, `images_verdict` i `metadata_verdict` (od 2026-10-14) odzwierciedlają wyniki porównania w poszczególnych kategoriach w compare.diff_summary (null bez porównania). Automatyczne przechwycenie z `alert_on: \"text\"` wysyła monitor.change_detected tylko wtedy, gdy text_verdict ma wartość changed (lub gdy nie ma wyniku porównania).\n\n`capture.anchor_confirmed` (tylko subskrypcje i GET /events): potwierdzono zakotwiczenie w Bitcoinie dowodu OpenTimestamps z pakietu, zwykle kilka godzin po przechwyceniu.\n\n```json\n{ \"event\": \"capture.anchor_confirmed\", \"capture_id\": \"cap_...\", \"schedule_id\": null, \"url\": \"https://example.com\",\n  \"confirmed_at\": \"...\", \"bitcoin_block_height\": 968549, \"upgraded_proof_available\": true, \"test_mode\": false }\n```\n\n`schedule.paused` (tylko subskrypcje i GET /events): ProofSnap wstrzymał automatyczne przechwycenie. `reason` to `no_billing` (brak dostępu do API), `plan_downgrade` (plan pozwala na mniej automatycznych przechwyceń, nie obejmuje on_change lub nie pozwala na nową pracę), `failures` (pięć nieudanych uruchomień z rzędu), `key_revoked` lub `invalid_cron`. Wstrzymanie wykonane przez Ciebie nie jest zdarzeniem.\n\n```json\n{ \"event\": \"schedule.paused\", \"schedule_id\": \"sch_...\", \"url\": \"https://example.com\", \"reason\": \"failures\",\n  \"paused_at\": \"...\", \"test_mode\": false }\n```\n\n`diff_status` to `ok` (compare zawiera porównanie), `no_baseline` (brak wcześniejszego przechwycenia do porównania) lub `failed` (porównanie nie powiodło się; compare ma wartość null)."
    },
    {
      "name": "Publiczne",
      "description": "Operacje, które nie wymagają klucza API."
    },
    {
      "name": "Import pakietu",
      "description": "Zaimportuj istniejące pakiety dowodowe ProofSnap (pliki ZIP utworzone przez rozszerzenie ProofSnap: przechwycenia stron internetowych, sesje wielokartowe, certyfikacje pojedynczych i połączonych plików; lub przez to API: przechwycenia, certyfikacje, porównania, zaczernienia) na swoje konto, aby były traktowane tak jak Twoje przechwycenia z API: przechowywane przez 10 lat i dostępne dla blokady prawnej, spraw i eksportów.\n\nTylko plany Legal (legal_solo, legal_seats), z produkcyjnym kluczem API lub sesją panelu z powiązanymi rozliczeniami (testowy klucz API, sesja bez rozliczeń i sandbox otrzymują 403 IMPORT_NOT_ALLOWED). Niepotwierdzony adres e-mail otrzymuje 403 EMAIL_NOT_VERIFIED, zaległa subskrypcja 402 PAYMENT_REQUIRED. Import nie jest przechwyceniem: nie wlicza się do miesięcznych limitów i nie ma opłaty za import; jest rozliczany wyłącznie jako przechowywane dane.\n\n**Przebieg.** 1. `POST /imports` z nazwą pliku i jego dokładnym rozmiarem w bajtach (maksymalnie 2 GiB): odpowiedź zawiera jeden wstępnie podpisany adres URL na każdą część o rozmiarze 32 MiB (ostatnia część jest mniejsza), ważny przez 1 godzinę. 2. Wyślij bajty każdej części metodą PUT na jej adres URL i zachowaj nagłówek odpowiedzi `ETag` każdej z nich. 3. `POST /imports/{import_id}/complete` ze wszystkimi numerami części i ich wartościami ETag. 4. Odpytuj `GET /imports/{import_id}`, dopóki status nie będzie końcowy.\n\n**Statusy.** `uploading` (oczekiwanie na przesłanie części i wywołanie complete; przesyłanie niezakończone w ciągu 24 godzin przechodzi w `expired`), `verifying` (pakiet jest sprawdzany), `accepted` (weryfikacja się powiodła: `capture_id` to nowe przechwycenie o tym samym id co import), `rejected` (weryfikacja się nie powiodła: `reasons` zawiera kody; poza rekordem importu nic nie jest przechowywane), `duplicate` (Twoje konto ma już przechwycenie z tym samym manifestem: `duplicate_of`), `expired`.\n\n**Weryfikacja.** Ścisła: podpis RSA nad manifest.json, każdy wymieniony plik obecny z podanym skrótem SHA-256 (oraz SHA-512 i rozmiarem, jeśli są podane), brak plików, których manifest nie wymienia (poza plikami dodawanymi przez system operacyjny, takimi jak .DS_Store, oraz dowodami wyprowadzonymi z manifestu), co najmniej jeden plik ze skrótem, spójny evidence id, ponownie obliczony łańcuch skrótów dziennika kryminalistycznego, dowód OpenTimestamps względem manifestu (offline) oraz kwalifikowany elektroniczny znacznik czasu eIDAS względem przypiętego certyfikatu głównego Disig z danymi walidacyjnymi zawartymi w pakiecie. Struktura ZIP jest sprawdzana przed odczytaniem czegokolwiek (bez szyfrowania, bez ZIP64, bez dowiązań, bezpieczne nazwy, bez powtórzonych nazw, rozmiary i CRC-32 każdej pozycji). Testowy pakiet API (ze znakiem wodnym) jest odrzucany. Pakiety API zapieczętowane, zanim zaczęto obliczać skrót evidence.pdf, są akceptowane z tym plikiem wymienionym w `verification.checks.files.unhashed`.\n\n**Poziomy** (`summary`): `integrity` (verified lub failed), `time_anchor` (eidas_qualified, eidas_qualified_revocation_unknown, ots_bitcoin_attested_unconfirmed, ots_pending, none) i `provenance`: dla pakietów API `issued_by_proofsnap` (manifest znajduje się w rejestrze pakietów wydanych przez API) lub `unverified`; dla pakietów rozszerzenia `registered_by_extension_account` (zalogowany użytkownik rozszerzenia zarejestrował ten manifest podczas jego tworzenia) lub `not_registered`; `unknown`, gdy nie udało się przeprowadzić wyszukiwania. Poziom provenance nigdy nie powoduje niepowodzenia importu.\n\n**Przechwycenie.** Zaakceptowany import jest przechwyceniem z `source` \"import\": można je pobrać (przesłany plik ZIP bajt po bajcie) i zweryfikować (POST /captures/{capture_id}/verify ponownie uruchamia te same kontrole), można nałożyć na nie blokadę prawną, przenieść je do kosza i trwale usunąć, pojawia się w sprawach (po zaimportowaniu z `matter_id` dziennik łańcucha dowodowego sprawy zapisuje `package.imported`) i w eksportach oraz jest przechowywane 10 lat od importu. Jego `captured_at` to czas podany przez sam pakiet (`import.captured_at_basis` \"package_claim\"), nigdy czas zaobserwowany przez ProofSnap; `created_at` to moment utworzenia przesyłania. Porównania i zaczernienia zaimportowanego pakietu są odrzucane (422 IMPORT_NOT_SUPPORTED).\n\n**Limity.** Maksymalnie 3 trwające importy (uploading lub verifying) na konto (409 IMPORT_LIMIT), 50 utworzonych importów na konto i dzień UTC (429 RATE_LIMITED, `details.action` \"import_create\"; liczy się każde żądanie utworzenia, także takie, które zostanie później odrzucone) oraz minutowy limit liczby żądań planu dla create i complete. Wolumen importu: maksymalnie 5 GiB na dzień UTC i 20 GiB na miesiąc UTC zadeklarowanych rozmiarów pakietów na konto, liczone przy create (a dla starszego przesyłania przy complete); po przekroczeniu 429 IMPORT_DAILY_BYTES_LIMIT lub IMPORT_MONTHLY_BYTES_LIMIT z `Retry-After`; anulowane przesyłanie nie jest liczone."
    }
  ],
  "paths": {
    "/captures": {
      "post": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "createCapture",
        "summary": "Przechwycenie adresu URL",
        "description": "Przyjmuje przechwycenie i od razu zwraca 202; przechwycenie działa w tle. Odpytuj `GET /captures/{capture_id}` lub przekaż `options.webhook_url`. Nazwa hosta z URL jest rozwiązywana i sprawdzana przed przyjęciem przechwycenia.\n\nLimit liczby żądań na konto w oknach 60-sekundowych (wszystkie klucze API konta i jego sesja w panelu dzielą jedno okno). Po zadziałaniu limitera odpowiedzi tej trasy zawierają nagłówki `RateLimit-Policy` i `RateLimit` (na podstawie IETF draft-ietf-httpapi-ratelimit-headers) oraz starsze `X-RateLimit-Limit`, `X-RateLimit-Remaining` i `X-RateLimit-Reset`. Odpowiedź 429 dodaje `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptureRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Przyjęte. Idempotentne powtórzenie wcześniejszego żądania z tym samym Idempotency-Key również zwraca 202, z nagłówkiem `Idempotent-Replay: true` i pierwotną treścią.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureAccepted"
                },
                "example": {
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "status": "processing",
                  "url": "https://example.com",
                  "poll_path": "/v1/captures/cap_a1B2c3D4e5F6g7H8",
                  "message": "Capture accepted. Poll GET /v1/captures/{capture_id} (or use webhook_url) for completion.",
                  "metadata": {}
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST, MISSING_FIELD, INVALID_URL, INVALID_TIMESTAMP_TYPE, INVALID_VIEWPORT, METADATA_TOO_LARGE lub INVALID_EVIDENCE_PACK_OPTIONS. WEBHOOK_REQUIRES_API_KEY: webhook_url wysłany z sesji panelu (webhooki wymagają klucza API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED (miesięczne maksimum lub przydział trybu testowego; zawiera nagłówki X-Quota-* lub X-Sandbox-*) albo PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas z testowym kluczem API (klucz ps_live_ konta bez powiązanego konta rozliczeniowego działa jako klucz testowy). EMAIL_NOT_VERIFIED: sesja w panelu, której adres e-mail nie został potwierdzony. PLAN_ARCHIVE_ONLY: plan jedynie przechowuje archiwum i nie tworzy nowej pracy. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja w panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (nowa praca w tym planie jest tworzona wyłącznie w panelu; łagodna bariera, a nie granica bezpieczeństwa).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED, TARGET_UNREACHABLE lub IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zbyt wiele żądań w bieżącym 60-sekundowym oknie. `error.details.retry_after_seconds` powtarza Retry-After. RateLimit zgłasza r=0 i liczbę sekund do zresetowania okna. RATE_LIMITED również dla sesji panelu Website Archive: jej dzienny limit 100 jednostek na dzień UTC, wspólny dla pojedynczych i wsadowych przechwyceń (jedna jednostka na URL), porównań, zaczernień i certify-hash (jedna jednostka na żądanie); żądanie, które się nie mieści, jest odrzucane w całości (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After i details.retry_after_seconds podają liczbę sekund do następnego dnia UTC, a minutowe nagłówki RateLimit i X-RateLimit-* są pomijane). Krótka odmowa zawierająca tylko details.retry_after_seconds i Retry-After: 1 oznacza równoczesną aktualizację licznika: ponów żądanie. Odmowa, która nie wynika z winy wywołującego, zwraca jednostki; jednostka zużyta na pracę przyjętą z 202, która później się nie powiedzie, nie jest zwracana. To miękka bramka, a nie granica bezpieczeństwa. Pojedyncze przechwycenie, którego Idempotency-Key jest obsługiwany z magazynu (powtórzenie, 409 lub 422), jest obsługiwane przed sprawdzeniem dziennego limitu i nie kosztuje żadnej jednostki.\n\nCONCURRENT_CAPTURE_LIMIT: zbyt wiele przechwyceń konta działa jednocześnie (developer 10, startup 25, growth 100, legal_solo i legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` i Retry-After informują, kiedy spróbować ponownie.\n\nTEST_MODE_DAILY_LIMIT: praca w trybie testowym (testowy klucz API lub sesja bez powiązanego rozliczenia) jest ograniczona do 200 jednostek na konto i dzień UTC; `details.used`, `details.limit`, `details.reset_at`, nagłówki `X-Test-Daily-*` i Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CAPTURE_FAILED (nie udało się rozpocząć przechwycenia; details.capture_id) lub INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "listCaptures",
        "summary": "Lista Twoich przechwyceń",
        "description": "Utworzone przez Ciebie wiersze, od najnowszych, we wszystkich statusach (w tym usunięte, czyli kosz; filtruj parametrem status). Każdy wiersz zawiera source, redacted_from, label i compared, więc jego typ można wyświetlić bez ponownego odczytu. Przechwycenie wykonane dla firmy jest wyświetlane tylko, dopóki nadal możesz na nim działać (zajmujesz miejsce w tej firmie lub jesteś jej właścicielem); strona może więc zawierać mniej wierszy niż limit, a next_cursor kontynuuje po ostatnim odczytanym wierszu. Uwzględniane są certyfikaty, porównania i zaczernienia.\n\nWyszukiwanie (tylko na własnej liście): url, host lub schedule_id w połączeniu z from / to i status pozwalają znaleźć rekord strony z danego dnia. Błędny filtr, dwa spośród url / host / schedule_id lub from, które nie jest wcześniejsze niż to, zwracają 400 MALFORMED_REQUEST.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Rozmiar strony, od 1 do 200 (domyślnie 50). Wartość spoza zakresu zwraca 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor z poprzedniej strony (format created_at|capture_id albo completed_at|capture_id przy order=completed_at). Przekaż go bez zmian.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Tylko wiersze w tym statusie: processing, completed, failed lub deleted (kosz). Każda inna wartość zwraca 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Tylko przechwycenia dokładnie tego adresu URL, w postaci, w jakiej został przechwycony (http lub https). Najwyżej jeden z parametrów url, host i schedule_id.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "Tylko przechwycenia, których URL znajduje się na tej nazwie hosta (bez względu na wielkość liter, np. www.example.com; subdomeny są osobnymi hostami). Najwyżej jeden z parametrów url, host i schedule_id. Wiersze utworzone przed 2026-10-05 mają host uzupełniony dopiero po wykonaniu uzupełnienia wstecznego.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "schedule_id",
            "in": "query",
            "required": false,
            "description": "Tylko przechwycenia wykonane przez to automatyczne przechwycenie. Najwyżej jeden z parametrów url, host i schedule_id.",
            "schema": {
              "type": "string",
              "pattern": "^sch_[A-Za-z0-9]{8,32}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Utworzone w tej chwili lub później: data (YYYY-MM-DD, północ UTC) lub znacznik czasu ISO 8601.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Utworzone przed tą chwilą (bez niej): data (YYYY-MM-DD, północ UTC) lub znacznik czasu ISO 8601. Musi być późniejsze niż from. Z schedule_id lub url najnowszy rekord sprzed danej chwili jest wersją, która wtedy obowiązywała.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "created_at (domyślnie: najnowsze utworzone jako pierwsze) lub completed_at (najnowsze zakończone jako pierwsze; tylko ze status=completed, w przeciwnym razie 400 MALFORMED_REQUEST). Przy completed_at parametry from i to ograniczają completed_at, a next_cursor ma postać completed_at|capture_id. Działa z url, host i schedule_id.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "completed_at"
              ],
              "default": "created_at"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Strona przechwyceń.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "captures": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "url": {
                            "type": "string",
                            "nullable": true
                          },
                          "status": {
                            "type": "string",
                            "nullable": true
                          },
                          "created_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "completed_at": {
                            "type": "string",
                            "nullable": true,
                            "description": "Kiedy przechwycenie zostało zakończone (null w trakcie przetwarzania lub po niepowodzeniu)."
                          },
                          "batch_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "schedule_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "test_mode": {
                            "type": "boolean",
                            "nullable": true
                          },
                          "external_archive": {
                            "allOf": [
                              {
                                "$ref": "#/components/schemas/ExternalArchive"
                              }
                            ],
                            "description": "Tylko wiersze completed. To samo co external_archive w GET /captures/{capture_id}."
                          },
                          "label": {
                            "type": "string",
                            "nullable": true,
                            "description": "Etykieta wiersza (certyfikaty: podana etykieta; zaczernienia: które przechwycenie zaczerniają)."
                          },
                          "compared": {
                            "type": "array",
                            "nullable": true,
                            "items": {
                              "type": "string"
                            },
                            "description": "Tylko porównania: identyfikatory dwóch przechwyceń źródłowych."
                          },
                          "source": {
                            "type": "string",
                            "nullable": true,
                            "description": "Sposób utworzenia wiersza: api (przechwycenie strony internetowej), schedule, certify-hash (certyfikat), compare (porównanie) lub redact (zaczernienie); null w wierszach starszych niż to pole."
                          },
                          "redacted_from": {
                            "type": "string",
                            "nullable": true,
                            "description": "Tylko zaczernienia: przechwycenie, z którego wykonano zaczernienie."
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Kiedy strona została przechwycona (zakończone wiersze). Dla zaczernienia czas przechwycenia jego przechwycenia źródłowego (redacted_at to chwila wykonania zaczernienia)."
                          },
                          "redacted_at": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Tylko zaczernienia: kiedy wykonano zaczernienie. captured_at zaczernienia to czas wykonania jego przechwycenia źródłowego."
                          },
                          "compared_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Tylko porównania: kiedy wykonano porównanie. Jego captured_at zawiera tę samą wartość (zachowane dla starszych klientów); używaj compared_at."
                          },
                          "manifest_hash": {
                            "type": "string",
                            "nullable": true,
                            "description": "sha256:<hex> pliku manifest.json pakietu (wiersze completed)."
                          },
                          "private_note": {
                            "type": "string",
                            "nullable": true,
                            "description": "Twoja prywatna notatka do przechwycenia (zobacz PrivateNote), nigdy nie jest częścią pakietu dowodowego."
                          },
                          "legal_hold": {
                            "type": "boolean",
                            "description": "Przechwycenie jest objęte blokadą prawną (dopóki wartość to true, nie jest usuwane; blokada jest zdejmowana automatycznie, gdy subskrypcja się zakończyła i upłynęło jej 30 dni). Zobacz PATCH /captures/{capture_id}."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "null, gdy ta strona ma mniej wierszy niż limit.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: nieprawidłowy cursor, status (jedna z wartości processing, completed, failed, deleted), limit (od 1 do 200) lub filtr wyszukiwania (url, host, schedule_id, from, to; najwyżej jeden z url / host / schedule_id; from wcześniejsze niż to).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/batch": {
      "post": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "createBatch",
        "summary": "Przechwycenie kilku adresów URL",
        "description": "Uruchamia jedno przechwycenie na każdy URL z tymi samymi opcjami. Każde przechwycenie jest niezależne: odpytuj każde capture_id osobno lub odbieraj jeden webhook na każde przechwycenie. Na każdy URL zużywa jedną jednostkę limitu liczby żądań i jedną jednostkę miesięcznego limitu. Partia z większą liczbą URL, niż pozwala jedno okno limitu liczby żądań, nigdy nie może się powieść, dlatego jest odrzucana z 400 RATE_LIMIT_COST_TOO_HIGH zamiast 429. Podziel ją na mniejsze żądania. Powtórzenie ukończonej partii (ten sam Idempotency-Key i ta sama treść) jest obsługiwane przed zastosowaniem limitu liczby żądań i nie zużywa żadnej jednostki tego limitu.\n\nLimit liczby żądań obowiązuje na konto w 60-sekundowych oknach (wszystkie klucze API konta i jego sesja panelu współdzielą jedno okno). Po zadziałaniu mechanizmu limitu odpowiedzi tego endpointu zawierają nagłówki `RateLimit-Policy` i `RateLimit` (oparte na projekcie IETF draft-ietf-httpapi-ratelimit-headers) oraz starsze `X-RateLimit-Limit`, `X-RateLimit-Remaining` i `X-RateLimit-Reset`. Odpowiedź 429 dodaje `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchRequest"
              },
              "example": {
                "urls": [
                  "https://example.com/a",
                  "https://example.com/b"
                ],
                "options": {
                  "case_reference": "Smith v. Jones"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Przyjęte. Idempotentne powtórzenie ukończonej partii z tym samym Idempotency-Key i tą samą treścią również zwraca 202, z nagłówkiem `Idempotent-Replay: true`; jest obsługiwane przed zastosowaniem limitu liczby żądań, więc nie zużywa jednostki limitu.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResult"
                }
              }
            }
          },
          "400": {
            "description": "INVALID_BATCH (details: reason i received albo index, url i zagnieżdżony obiekt reason dla nieprawidłowego adresu URL), MALFORMED_REQUEST lub błąd opcji. WEBHOOK_REQUIRES_API_KEY: webhook_url wysłany z sesji panelu (webhooki wymagają klucza API). RATE_LIMIT_COST_TOO_HIGH: więcej adresów URL, niż pozwala jedno okno limitu liczby żądań; details { reason, max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED lub PAYMENT_REQUIRED. Partia jest również odrzucana z QUOTA_EXCEEDED, gdy w danej chwili nie można odczytać miesięcznego zużycia; ponów żądanie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY: include_eidas z testowym kluczem API. EMAIL_NOT_VERIFIED: sesja w panelu, której adres e-mail nie został potwierdzony. PLAN_ARCHIVE_ONLY: plan jedynie przechowuje archiwum i nie tworzy nowej pracy. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja w panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (nowa praca w tym planie jest tworzona wyłącznie w panelu; łagodna bariera, a nie granica bezpieczeństwa).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zbyt wiele żądań w bieżącym 60-sekundowym oknie. `error.details.retry_after_seconds` powtarza Retry-After. RateLimit zgłasza r=0 i liczbę sekund do zresetowania okna. RATE_LIMITED również dla sesji panelu Website Archive: jej dzienny limit 100 jednostek na dzień UTC, wspólny dla pojedynczych i wsadowych przechwyceń (jedna jednostka na URL), porównań, zaczernień i certify-hash (jedna jednostka na żądanie); żądanie, które się nie mieści, jest odrzucane w całości (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After i details.retry_after_seconds podają liczbę sekund do następnego dnia UTC, a minutowe nagłówki RateLimit i X-RateLimit-* są pomijane). Krótka odmowa zawierająca tylko details.retry_after_seconds i Retry-After: 1 oznacza równoczesną aktualizację licznika: ponów żądanie. Odmowa, która nie wynika z winy wywołującego, zwraca jednostki; jednostka zużyta na pracę przyjętą z 202, która później się nie powiedzie, nie jest zwracana. To miękka bramka, a nie granica bezpieczeństwa.\n\nCONCURRENT_CAPTURE_LIMIT: żaden URL z partii nie mógł się rozpocząć, ponieważ konto ma już uruchomionych tyle przechwyceń, ile może (developer 10, startup 25, growth 100, legal_solo i legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` i Retry-After. Gdy mieszczą się tylko niektóre, partia jest przyjmowana z 202, a pozostałe URL są wymienione w `not_admitted`.\n\nTEST_MODE_DAILY_LIMIT: praca w trybie testowym (testowy klucz API lub sesja bez powiązanego rozliczenia) jest ograniczona do 200 jednostek na konto i dzień UTC, jedna na URL; `details.used`, `details.limit`, `details.reset_at`, nagłówki `X-Test-Daily-*` i Retry-After.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        }
      }
    },
    "/captures/hosts": {
      "get": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "listCaptureHosts",
        "summary": "Lista przechwyconych nazw hostów",
        "description": "Wszystkie nazwy hostów z Twojej własnej listy przechwyceń, z liczbą przechwyceń i czasem ostatniego przechwycenia dla każdego hosta, do filtrowania według domeny: przekaż host z powrotem jako GET /captures?host=. count to liczba wierszy, które zwraca GET /captures?host=<host> bez status (spośród odczytanych przechwyceń): zarówno przechwycenia testowe, jak i produkcyjne, każdy status łącznie z przeniesionymi do kosza (status deleted), tak jak pokazuje je lista; trwale usunięte przechwycenia i wiersze bez adresu URL strony (certyfikaty) są pomijane. Przechwycenie jest liczone, gdy ma zapisaną nazwę hosta (przechwycenia utworzone przed 2026-10-05 mają ją po zakończeniu uzupełniania wstecznego). Przechwycenie firmowe jest liczone tylko wtedy, gdy nadal możesz na nim działać (masz miejsce w tej firmie lub jesteś jej właścicielem). host jest zapisany małymi literami. Sortowanie według count (od najwyższej wartości), a następnie według host (od A do Z). Maksymalnie 1000 hostów. Odczytywanych jest 5000 najnowszych przechwyceń; truncated ma wartość true, gdy hostów jest więcej niż 1000 lub istnieją przechwycenia starsze niż te 5000, więc może brakować hosta przechwyconego wyłącznie przed nimi. Lista jest obliczana najwyżej raz na 30 sekund dla każdego konta i może mieć do 30 sekund (nieco więcej, gdy oblicza ją właśnie inne żądanie tego konta). Obliczenie kosztuje jedną jednostkę Twojego minutowego limitu liczby żądań; odpowiedź z tej pamięci podręcznej nic nie kosztuje. Dostępne w każdym planie, dla kluczy API i sesji panelu.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Nazwy hostów.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "hosts",
                    "truncated"
                  ],
                  "properties": {
                    "hosts": {
                      "type": "array",
                      "maxItems": 1000,
                      "items": {
                        "type": "object",
                        "required": [
                          "host",
                          "count",
                          "last_captured_at"
                        ],
                        "properties": {
                          "host": {
                            "type": "string",
                            "example": "www.example.com",
                            "description": "Nazwa hosta małymi literami, wartość przyjmowana przez GET /captures?host=."
                          },
                          "count": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 12,
                            "description": "Przechwycenia tego hosta spośród odczytanych przechwyceń: wiersze, które zwraca GET /captures?host= bez status, łącznie z przeniesionymi do kosza."
                          },
                          "last_captured_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "example": "2026-10-05T07:48:38Z",
                            "description": "Czas ostatniego przechwycenia tego hosta (kiedy strona została przechwycona, a w przeciwnym razie kiedy zlecono przechwycenie), UTC, z dokładnością do sekundy."
                          }
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true, gdy lista może być niekompletna: ponad 1000 hostów albo więcej przechwyceń niż odczytane najnowsze 5000."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: scope nie jest obsługiwany (tylko własne przechwycenia).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "RATE_LIMITED: wyczerpano minutowy limit liczby żądań (obliczenie listy kosztuje jedną jednostkę) albo lista Twojego konta jest właśnie obliczana przez inne żądanie i nie zapisano żadnego wcześniejszego wyniku; ponów po Retry-After sekundach.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "getCapture",
        "summary": "Pobranie przechwycenia",
        "description": "Status oraz, po ukończeniu, świeży package_url ważny do 24 godzin (zob. package_url_expires_at). Działa dla identyfikatorów przechwyceń, certyfikatów, porównań i zaczernień. Odczytać go może konto, które go utworzyło, członkowie sprawy, do której należy, oraz właściciel firmy, dla której został wykonany (jeśli subskrypcja firmy się zakończyła, do zamknięcia jej okna pobierania).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Wyślij download, gdy pobierasz pakiet: w przypadku przechwycenia w sprawie powoduje to zapisanie capture.downloaded w dzienniku dostępu sprawy. Zwykłe odpytywanie o status powinno go pomijać.",
            "schema": {
              "type": "string",
              "enum": [
                "download"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Przechwycenie.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: to nie jest Twoje przechwycenie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: nieznany lub nieprawidłowy identyfikator, usunięte przechwycenie albo nie znaleziono pliku pakietu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "deleteCapture",
        "summary": "Usunięcie przechwycenia",
        "description": "Domyślnie: przenosi przechwycenie do kosza. Można je przywrócić przez PATCH do restorable_until (30 dni); potem jest trwale usuwane. Z permanent=true zapisany pakiet jest usuwany od razu, a rekord zostaje zredukowany do znacznika usunięcia; tej operacji nie można cofnąć. Żadna z opcji nie usuwa pakietów, które już pobrano, ani dowodu OpenTimestamps. Przechwycenie w otwartej lub zarchiwizowanej sprawie jest najpierw usuwane ze sprawy (co zostaje zapisane w jej dzienniku łańcucha dowodowego), jeśli wywołujący ma dostęp do tej sprawy; wywołujący bez dostępu (autor po usunięciu ze sprawy) otrzymuje 409 MATTER_LINKED, zarówno przy przeniesieniu do kosza, jak i przy permanent=true, dopóki właściciel sprawy nie usunie przechwycenia ze sprawy; przechwycenia w zapieczętowanej sprawie nie można usunąć. Przechwycenia, które jest wciąż przetwarzane, nie można usunąć (409 CAPTURE_PROCESSING); usuń je, gdy się zakończy lub zakończy niepowodzeniem. Usunięcie przechwycenia, które nigdy się nie zakończyło, zwraca jego jednostkę miesięcznego limitu.\n\nOkres ochronny po zdjęciu blokady prawnej: przez 14 dni od zdjęcia blokady prawnej (`deletion_blocked_until` w GET) przechwycenie nie jest trwale usuwane (409 CAPTURE_HOLD_GRACE z `details.until`), ani przez osobę, ani przez opróżnianie kosza czy okres przechowywania (usunięcie konta i firmy po zakończeniu subskrypcji na to nie czeka). Przeniesienie do kosza jest dozwolone; jego opróżnienie czeka do końca okresu ochronnego. Trwałe usunięcie najpierw zdejmuje blokadę prawną S3 Object Lock, która nadal pozostała na zapisanych obiektach; gdy magazyn odmówi, żądanie zwraca 409 CAPTURE_LEGAL_HOLD (\"still locked in storage\", spróbuj ponownie później) i nic nie zostaje usunięte. Każde przeniesienie do kosza, przywrócenie i trwałe usunięcie jest zapisywane w łańcuchu zdarzeń przechwyceń konta (GET /captures/events). Zaczernienie lub porównanie, którego przechwycenie źródłowe jest objęte blokadą prawną, zwraca `source_under_legal_hold: true`, gdy zostanie przeniesione do kosza lub usunięte.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "permanent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Usunięto. Kosz: { capture_id, status: \"deleted\", restorable: true, restorable_until }. Trwale: { capture_id, status: \"deleted\", permanent: true }. Już w koszu: { capture_id, status }.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restorable": {
                      "type": "boolean"
                    },
                    "restorable_until": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "permanent": {
                      "type": "boolean"
                    },
                    "source_under_legal_hold": {
                      "type": "boolean",
                      "description": "Występuje (true) tylko wtedy, gdy jest to zaczernienie lub porównanie utworzone z przechwycenia objętego blokadą prawną."
                    },
                    "deletion_blocked_until": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Kosz w okresie karencji po zdjęciu blokady: opróżnienie kosza czeka do tej chwili."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: to nie jest Twoje przechwycenie. FORBIDDEN także wtedy, gdy przechwycenie należy do sprawy, która nie jest zapieczętowana, a wywołujący (właściciel firmy działający na przechwyceniu użytkownika jednego z miejsc, a nie jego autor) nie może otworzyć tej sprawy. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED: przechwycenie jest dowodem w zapieczętowanej sprawie. MATTER_LINKED: przechwycenie jest dowodem w sprawie, do której wywołujący już nie należy; najpierw musi je usunąć właściciel sprawy. CAPTURE_PROCESSING: przechwycenie jest nadal przetwarzane (error.details.status). CAPTURE_LEGAL_HOLD: przechwycenie jest objęte blokadą prawną i nikt nie może go usunąć (ani przenieść do kosza, ani trwale wymazać). CAPTURE_HOLD_GRACE: trwałe usunięcie w okresie karencji po zdjęciu blokady prawnej z przechwycenia (LEGAL_HOLD_RELEASE_GRACE_DAYS, 14 dni); error.details.until podaje, od kiedy będzie to możliwe. Przeniesienie do kosza jest w okresie karencji dozwolone.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: nie udało się trwale usunąć zapisanego pakietu; niczego nie usunięto.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "restoreCapture",
        "summary": "Przywrócenie usuniętego przechwycenia, ustawienie prywatnej notatki albo nałożenie lub zdjęcie blokady prawnej",
        "description": "Przywraca przechwycenie z kosza. Jeśli przy usunięciu zostało odłączone od sprawy, jest do niej ponownie dodawane, o ile sprawa jest nadal otwarta, a wywołujący nadal może ją otworzyć; tylko wtedy odpowiedź zawiera matter_id i matter_relinked (matter_id jest null, gdy ponowne powiązanie nie nastąpiło). Wywołujący, który nie może już otworzyć tej sprawy, odzyskuje przechwycenie bez niej, w dzienniku łańcucha dowodowego sprawy nic nie jest zapisywane, a odpowiedź nie wspomina o sprawie. Przechwycenie, które nie jest w koszu, jest zwracane bez zmian.\n\nPrywatna notatka: treść z `private_note` (ciąg o długości maksymalnie 200 znaków albo null, aby ją usunąć) zamiast tego ustawia prywatną notatkę przechwycenia i nie robi nic więcej; nie można jej łączyć z `action`. Notatka jest przechowywana wyłącznie w rekordzie przechwycenia, poza zapieczętowanym pakietem dowodowym, więc jej zmiana nigdy nie zmienia dowodów ani ich skrótów. Ustawić ją może tylko twórca przechwycenia (właściciel firmy i członkowie sprawy otrzymują 403), w każdym stanie przechwycenia poza trwale usuniętym (404). Odpowiedź to { capture_id, status, private_note }.\n\nBlokada prawna: treść `{ \"action\": \"legal_hold\", \"enabled\": true|false, \"reason\"?: string }` nakłada (true) lub zdejmuje (false) blokadę prawną przechwycenia i nie robi nic więcej. Przechwycenie objęte blokadą prawną nie jest usuwane: DELETE (do kosza lub trwale) zwraca każdemu 409 CAPTURE_LEGAL_HOLD i nie obejmuje go ani opróżnianie kosza, ani usuwanie po okresie przechowywania; GET pokazuje `storage.retention.earliest_deletion_at` jako null. Blokada obowiązuje tylko, dopóki konto ma plan, który przechowuje dane (dowolny płatny plan, w tym Archive only; w przypadku przechwycenia firmowego: firma jest aktywna lub utrzymywana jako archiwum firmowe). Po zakończeniu subskrypcji przechwycenia pozostają dostępne przez 30 dni; jeśli do tego czasu taki plan nie zostanie kupiony, wszystkie blokady prawne konta (lub firmy) są w tym dniu automatycznie zdejmowane (aktor system, powód subscription_ended, odnotowane jak każde inne zdjęcie, bez okresu ochronnego po zdjęciu), a przechwycenia są usuwane wraz z pozostałymi. Aby zachować zablokowane przechwycenia, przed tą datą przejdź na Archive only (lub kup archiwum firmowe) albo je pobierz. W ciągu tych 30 dni nie można nałożyć nowej blokady (konto nie ma płatnego planu), a istniejące blokady nadal można zdjąć. Blokada jest przechowywana wyłącznie w rekordzie przechwycenia, poza zapieczętowanym pakietem dowodowym. `reason` to opcjonalny zwykły tekst o długości maksymalnie 200 znaków (bez znaków sterujących, niewidocznych ani znaków kierunku tekstu; białe znaki są scalane, pusta wartość oznacza brak powodu); jest przechowywany razem z blokadą i usuwany przy jej zdjęciu.\n\nKto: nałożyć blokadę może każdy, kto może działać na przechwyceniu (jego autor, w przypadku przechwycenia firmowego tylko dopóki zajmuje miejsce w tej firmie, oraz właściciel firmy, także firmy, której subskrypcja się zakończyła, dopóki trwa jej okres przechowywania lub jest utrzymywana jako archiwum firmowe; archiwum tylko przez sesję panelu). Zdjąć ją może: w przypadku przechwycenia osobistego jego autor, w przypadku przechwycenia firmowego wyłącznie właściciel firmy (użytkownik miejsca otrzymuje 403 LEGAL_HOLD_OWNER_ONLY). Nałożenie blokady wymaga płatnego planu (każdego płatnego planu, w tym Archive only): bezpłatny tryb testowy (sandbox), a także konto, którego subskrypcja się zakończyła, otrzymuje 403 LEGAL_HOLD_NOT_AVAILABLE z details.reason \"plan\" (działające archiwum firmowe liczy się jako płatne dla przechwyceń tej firmy, przez sesję panelu jej właściciela), a przechwycenie testowe (ze znakiem wodnym, wykonane testowym kluczem API lub bez powiązanego rozliczenia) otrzymuje 403 LEGAL_HOLD_NOT_AVAILABLE z details.reason \"test_capture\". Zdjęcie blokady działa w każdym planie, także po zakończeniu subskrypcji. W planie Website Archive klucz API jest tylko do odczytu (403 PLAN_API_READ_ONLY), sesja panelu może to zrobić. Zaległa lub nieopłacona subskrypcja tego nie blokuje. Sesja panelu lub klucz, których adres e-mail nie został potwierdzony, otrzymują 403 EMAIL_NOT_VERIFIED.\n\nOdrzucane dla przechwycenia w koszu (409 CAPTURE_IN_TRASH: najpierw je przywróć) lub nadal przetwarzanego (409 CAPTURE_PROCESSING); przechwycenie trwale usunięte albo takie, którego trwałe usuwanie jest w toku, zwraca 404. Każde żądanie dotyczące blokady prawnej zużywa jedną jednostkę minutowego limitu liczby żądań konta (po jego wyczerpaniu 429 RATE_LIMITED z Retry-After). Wysłanie stanu, który przechwycenie już ma, niczego nie zmienia i zwraca 200 z zapisanymi polami oraz `changed: false`. Każda zmiana jest zapisywana w dzienniku audytu jako capture.legal_hold (dla przechwycenia firmowego w dzierżawie firmy), odnotowywana w historii dostępu do przechwycenia (`legal_hold_on` / `legal_hold_off`, zachowywana także po późniejszym wymazaniu przechwycenia), a gdy przechwycenie należy do sprawy, zapisywana w dzienniku łańcucha dowodowego sprawy jako capture.legal_hold_placed lub capture.legal_hold_released z identyfikatorem przechwycenia jako celem (powód trafia do dziennika dostępu sprawy, nigdy do łańcucha skrótów; łańcuch zapieczętowanej sprawy się nie zmienia). Odpowiedź to { capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }.\n\nBlokada prawna v2: nałożenie lub zdjęcie blokady ustawia lub znosi także blokadę prawną S3 Object Lock na każdej wersji przechowywanego pakietu, jego zaktualizowanym dowodzie OpenTimestamps i osobno przechowywanej kopii zapasowej (`s3_legal_hold` w GET; gdy magazyn odmówi lub nie ma konfiguracji Object Lock, blokada w rekordzie przechwycenia nadal obowiązuje, a nieudaną próbę serwer ponawia). Zdjęcie rozpoczyna 14-dniowy okres ochronny, w którym przechwycenie nie jest trwale usuwane (nie dotyczy to sytuacji po zakończeniu subskrypcji: zastępuje go wtedy 30 dni od zakończenia). Blokadę nałożoną przez blokadę prawną sprawy (`legal_hold_source` \"matter:<id>\") zdejmuje się w sprawie, dopóki ta blokada obowiązuje: zdjęcie jej tutaj zwraca 409 LEGAL_HOLD_MATTER (error.details.matter_id dla osoby z dostępem do odczytu sprawy). Każda zmiana jest odnotowywana w łańcuchu zdarzeń przechwyceń konta (GET /captures/events).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "restore (domyślnie dla treści bez private_note) lub legal_hold (z enabled i opcjonalnym reason).",
                    "enum": [
                      "restore",
                      "legal_hold"
                    ]
                  },
                  "private_note": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PrivateNote"
                      }
                    ],
                    "description": "Ustawia (ciąg) lub czyści (null lub pusty ciąg) prywatną notatkę. Nie można łączyć z action."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "Z action legal_hold (tam wymagane): true nakłada blokadę prawną, false ją zdejmuje."
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 200,
                    "description": "Z action legal_hold, opcjonalne: dlaczego przechwycenie jest zablokowane, zwykły tekst o długości najwyżej 200 znaków. Zapisywany razem z blokadą, poza pakietem dowodowym; ignorowany przy zdejmowaniu blokady (trafia wtedy tylko do dziennika audytu)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Przywrócono ({ capture_id, status, restored: true, matter_id?, matter_relinked? }), bez zmian ({ capture_id, status }), ustawiono prywatną notatkę ({ capture_id, status, private_note }) lub ustawiono blokadę prawną ({ capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string"
                    },
                    "restored": {
                      "type": "boolean"
                    },
                    "matter_id": {
                      "type": "string",
                      "nullable": true
                    },
                    "matter_relinked": {
                      "type": "boolean"
                    },
                    "private_note": {
                      "type": "string",
                      "nullable": true
                    },
                    "legal_hold": {
                      "type": "boolean",
                      "description": "Operacja blokady prawnej: blokada przechwycenia po wykonaniu żądania."
                    },
                    "legal_hold_set_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Operacja blokady prawnej: kiedy nałożono bieżącą blokadę (null, gdy jej nie ma)."
                    },
                    "legal_hold_reason": {
                      "type": "string",
                      "nullable": true,
                      "description": "Operacja blokady prawnej: powód zapisany przy bieżącej blokadzie lub null."
                    },
                    "changed": {
                      "type": "boolean",
                      "description": "Operacja blokady prawnej: false, gdy przechwycenie miało już żądany stan (nic nie zostało zmienione ani zapisane w dzienniku)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: nieprawidłowy JSON, treść, która nie jest obiektem, action inna niż restore lub legal_hold, private_note razem z action albo private_note, który nie jest ciągiem znaków ani null, jest dłuższy niż 200 znaków lub zawiera znaki sterujące, niewidoczne albo znaki kierunku tekstu. Dla legal_hold: brak enabled lub enabled, które nie jest wartością logiczną (boolean), reason, który nie jest ciągiem znaków, jest dłuższy niż 200 znaków lub nie jest zwykłym tekstem, albo nieznane pole.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: to nie jest Twoje przechwycenie. FORBIDDEN także wtedy, gdy przechwycenie należy do sprawy, która nie jest zapieczętowana, a wywołujący (właściciel firmy działający na przechwyceniu użytkownika jednego z miejsc, a nie jego autor) nie może otworzyć tej sprawy. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). Dla private_note: FORBIDDEN dla każdego poza autorem przechwycenia (który nadal musi mieć prawo do działania na nim). Dla legal_hold: FORBIDDEN, gdy wywołujący nie może działać na przechwyceniu; LEGAL_HOLD_OWNER_ONLY, gdy blokadę prawną przechwycenia firmowego zdejmuje użytkownik miejsca (a nie właściciel firmy); LEGAL_HOLD_NOT_AVAILABLE przy nakładaniu blokady bez płatnego planu (details.reason plan) lub na przechwycenie w trybie testowym (details.reason test_capture); EMAIL_NOT_VERIFIED przy niepotwierdzonym adresie e-mail. Powyższa reguła dotycząca spraw nie ma zastosowania do blokady prawnej.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND, także dla przechwycenia usuniętego trwale.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Tylko blokada prawna. CAPTURE_IN_TRASH: przechwycenie jest w koszu; najpierw je przywróć. CAPTURE_PROCESSING: przechwycenie jest nadal przetwarzane (error.details.status). LEGAL_HOLD_MATTER: blokadę przechwycenia nałożyła blokada prawna jego sprawy, która nadal obowiązuje; zdejmij ją w sprawie. LEGAL_HOLD_SOURCE: blokada zaczernienia lub porównania, która podąża za przechwyceniem, z którego powstało (w przypadku porównania za którymkolwiek z porównywanych przechwyceń), a to przechwycenie nadal jest objęte blokadą prawną (nałożoną bezpośrednio na nie lub przez jego sprawę): pakiet pozostaje zablokowany, dopóki ta blokada nie zostanie zdjęta (`details.source_capture_id`). Porównanie jest zablokowane, dopóki zablokowane jest którekolwiek z porównywanych przechwyceń, i zostaje zwolnione dopiero wtedy, gdy żadne nie jest zablokowane (`legal_hold_source` \"derived:<capture id>\" wskazuje to zablokowane).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Tylko blokada prawna. RATE_LIMITED: minutowy limit liczby żądań konta został wyczerpany. `error.details.retry_after_seconds` powtarza Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/verify": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "verifyCapture",
        "summary": "Weryfikacja przechowywanego pakietu",
        "description": "Odczytuje zapisany pakiet i sprawdza podpis RSA pliku manifest.json oraz skrót SHA-256 każdego wymienionego w nim pliku. Dla pakietów większych niż 256 MiB zwracany jest 413 VERIFY_PACKAGE_TOO_LARGE; zweryfikuj je offline za pomocą dołączonych skryptów. Każde żądanie kosztuje jedną jednostkę minutowego limitu liczby żądań konta (429 RATE_LIMITED). Gdy pakiet zawiera manifest.json.tsr, ProofSnap sprawdza również token znacznika czasu eIDAS (checks.eidas): podpis tokenu i podpisane atrybuty, to, czy zawiera skrót SHA-256 pliku manifest.json, extendedKeyUsage certyfikatu TSA, jego łańcuch certyfikatów do przypiętego głównego CA Disig QCA4 według czasu tokenu (genTime) oraz status certyfikatu TSA na podstawie odpowiedzi OCSP lub listy CRL zapisanej przy wydaniu w eidas_validation.json (weryfikowanej podpisem, a nie pobieranej na bieżąco). Ta kontrola nigdy nie żąda nowego znacznika czasu. Informuje też, czy obecny jest manifest.json.ots i czy skrót manifestu znajduje się w rejestrze wydań ProofSnap. Werdykt zależy wyłącznie od podpisu manifestu i wymienionych plików; token, który nie przejdzie kontroli, jest raportowany w checks.eidas i w warnings (EIDAS_TOKEN_FAILED), ale nie wpływa na werdykt. Pakiet wydany z kwalifikowanym znacznikiem czasu zapisuje eidas_requested: true w swoim podpisanym manifest.json; gdy taki pakiet nie zawiera już manifest.json.tsr, checks.eidas i checks.eidas_disig są raportowane jako missing z ostrzeżeniem EIDAS_TOKEN_MISSING (dołączone skrypty weryfikacyjne zgłaszają FAIL dla tego samego pakietu). Dozwolone dla właściciela przechwycenia, członków jego sprawy i właściciela firmy, dla której zostało wykonane. Aby niezależnie sprawdzić znaczniki czasu, uruchom skrypty z folderu verification/ w pakiecie.\n\nZaimportowane pakiety (source import): zapisany ZIP jest ponownie sprawdzany kontrolami importu (podpis, każdy plik, łańcuch dowodowy, łańcuch dziennika forensycznego, znaczniki czasu; pochodzenie jest dla pakietów API ponownie wyszukiwane w rejestrze API, a dla pakietów z rozszerzenia przejmowane z rekordu importu). Odpowiedź to { capture_id, source, type: \"import\", package_kind, verifier: \"package-verify\", verdict (match, modified, invalid), rechecked: true, manifest_sha256, zip_sha256, zip_matches_import, kind, summary, levels, checks, reasons, provenance }. Pakiet powyżej 512 MiB lub kontrola, która nie zakończy się w ciągu około 20 sekund, zwraca rechecked: false z przyczyną (too_large_for_synchronous_check, time_budget, storage_error), verdict: null i stored_verification (raport zapisany w chwili importu).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Wynik weryfikacji.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyResult"
                },
                "example": {
                  "verdict": "match",
                  "capture_id": "cap_a1B2c3D4e5F6g7H8",
                  "source": "s3",
                  "checks": {
                    "manifest_signature": {
                      "status": "valid",
                      "algorithm": "RSA-4096"
                    },
                    "declared_files": {
                      "status": "all_match"
                    },
                    "bitcoin_ots": {
                      "status": "present"
                    },
                    "eidas_disig": null,
                    "proofsnap_issuance": {
                      "status": "issued_by_proofsnap",
                      "manifest_sha256": "3f2a...",
                      "issued_at": "2026-09-01T10:00:00.000Z",
                      "type": "capture"
                    },
                    "eidas": null
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: nieznane, usunięte lub bez zapisanego pakietu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "VERIFY_PACKAGE_TOO_LARGE: przechowywany pakiet przekracza 256 MiB.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: wyczerpano minutowy limit liczby żądań konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: nie udało się odczytać zapisanego pakietu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/redact": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Porównanie i zaczernianie"
        ],
        "operationId": "redactCapture",
        "summary": "Utworzenie zaczernionej kopii",
        "description": "Tworzy nowy pakiet z ukończonego przechwycenia z usuniętą wybraną treścią, ponownie go podpisuje (nowy klucz RSA-4096) i przekazuje do zakotwiczenia w OpenTimestamps. Oryginał nie jest zmieniany. Synchronicznie: zwraca 201, gdy pakiet jest gotowy. Liczone jako jedno przechwycenie.\n\n- Zaczernienia prostokątami (boxes) zaczerniają obszary zrzutu ekranu. Zrzut ekranu JPEG w postaci, w jakiej zapisuje go przechwycenie (sekwencyjny, w skali szarości lub YCbCr), jest zaczerniany bez dekodowania do pikseli: skompresowane bloki (MCU, od 8 do 32 pikseli na bok), których dotyka prostokąt, są zastępowane czarnymi blokami, a każdy inny blok pozostaje bez zmian, więc reszta obrazu nie jest ponownie kompresowana (`screenshot_method` `jpeg_dct_blocks`, do 150 megapikseli). Zaczerniony obszar to każdy prostokąt zaokrąglony na zewnątrz do pełnych bloków (`painted_rects`), więc zawsze obejmuje cały prostokąt. Zrzut ekranu PNG lub JPEG innego rodzaju jest dekodowany, zamalowywany i ponownie kodowany jako screenshot.jpeg, a następnie w nowym pliku zaczerniane są bloki 8 x 8, których dotyka prostokąt (`screenshot_method` `decode_repaint_jpeg`, do 60 megapikseli). Zrzut ekranu powyżej swojego limitu lub z nieczytelnymi danymi obrazu jest odrzucany z kodem 422 REDACTION_SCREENSHOT_UNREADABLE, zanim cokolwiek zostanie naliczone. Jeśli nagłówek jest czytelny, a dane obrazu nie, błąd jest wykrywany po zarezerwowaniu miesięcznej jednostki i ta jednostka jest zwalniana. Zaczernienia tekstu i metadanych działają również bez prostokątów. redaction_manifest.json (`screenshot_boxes`) zapisuje metodę, `painted_rects`, siatkę bloków i notę wyjaśniającą dla biegłych.\n- Zaczernienia tekstu usuwają dopasowania z page.html i domtextcontent.txt, z każdej wartości tekstowej w metadata.json (tytuł strony i wartości meta, rekordy WHOIS / RDAP, podmiot TLS, wartości nagłówków odpowiedzi HTTP) oraz z każdej wartości tekstowej w forensic_log.json i chain_of_custody.json (case_reference, case_id, case_note, Twoje wartości metadata, wstrzyknięty kod i jego błędy, etykiety zamkniętych okienek, legal_context.note). Identyfikatory, skróty, czasy i URL w tych rekordach pozostają bez zmian. Usuwane są także zakodowane wystąpienia: odwołania do znaków HTML (nazwane, dziesiętne, szesnastkowe, takie jak M&uuml;ller lub jan&#64;firma.cz), kodowanie procentowe (mailto:jan%40firma.cz) i sekwencje ucieczki JavaScript (@, \\x40), w tym kodowania zagnieżdżone do dwóch poziomów; znacznik zastępuje same zakodowane znaki. Kontrola po zaczernieniu, szukająca pozostałych dopasowań, czyta pliki JSON wartość po wartości i przeszukuje każdy tekst zarówno w postaci zapisanej, jak i zdekodowanej, więc kończy się niepowodzeniem przy dopasowaniu w dowolnej z tych postaci. Zaczernienia tekstu zaczerniają również zrzut ekranu w miejscach, w których text_layout.json przechwycenia źródłowego umieszcza wystąpienie (`text_rule_rects`): zamalowywane są szacowane wiersze bloku tekstu zawierające dopasowanie, a cały blok tylko wtedy, gdy oszacowanie jest niepewne (liczone w `screenshot_boxes.from_text_rules.rects_whole_block` w redaction_manifest.json); wystąpienia, których nie umiejscowi, są liczone w `text_occurrences_not_located`. Reguły dopasowują dokładną wielkość liter, chyba że ustawiono `case_insensitive`.\n- Zaczernienia metadanych usuwają pola z metadata.json. Dwie nazwy sięgają dalej. `ip_dns` (akceptowane także: `ip`, `dns`, `resolved_ips`) usuwa adresy IP docelowej witryny i odpowiedzi DNS z metadata.json, forensic_log.json i chain_of_custody.json (własne adresy maszyny przechwytującej pozostają), a także usuwa je, tylko jako całe adresy, z page.html, domtextcontent.txt i zrzutu ekranu jako automatyczne reguły tekstu (zapisywane wyłącznie ze źródłem ip_dns, nigdy z adresem ani jego skrótem, i niewliczane do limitu text_redactions). `cookie_names` (także `cookie`, `cookies`, `http_headers.cookie`) usuwa nagłówki Cookie i Set-Cookie oraz każdą listę nazw plików cookie w tych rekordach; liczby pozostają. Wartości plików cookie są zaczerniane już w każdym przechwyceniu.\n\nProstokąt zakrywa tylko piksele. Tekst pod prostokątem pozostaje w page.html, domtextcontent.txt i metadata.json, chyba że dodasz także zaczernienie tekstu, a obraz pod prostokątem może nadal być wskazywany lub osadzony w page.html (img src lub srcset, CSS url(), URI data:); zaczernienie tekstu dopasowujące jego adres usuwa to odwołanie. Zaczerniony pakiet zawiera z oryginału tylko screenshot.jpeg (lub screenshot.png, gdy żaden prostokąt go nie zastąpił), page.html, domtextcontent.txt, metadata.json i forensic_log.json, a do tego pliki, które zapisuje od nowa (evidence.pdf, chain_of_custody.json, redaction_manifest.json, pliki weryfikacyjne i pliki manifestu). Każdy inny plik oryginału, taki jak images/, image_inventory.json, text_layout.json, page.mhtml, network.har i capture.warc, jest pomijany i wymieniany w redaction_manifest.json pod dropped_for_leak_protection. Zaczernienia tekstu są stosowane również do tych nazw plików, ponieważ kopia obrazu jest nazwana według swojego URL. chain_of_custody.json zachowuje zdarzenia oryginału dotyczące tych plików, oznaczone artifact_removed_in_redaction i bez skrótów plików, których zaczerniony pakiet nie zawiera. Usuwany jest też rule_902_certification.pdf oryginału, ponieważ przytacza pliki i skróty oryginału; usunięcie jest wymieniane w redaction_manifest.json (removed_as_stale) i w chain_of_custody.json. URL przechwycenia nigdy nie jest zaczerniany, ponieważ należy do podpisanego manifestu: zaczernienie tekstu, którego tekst występuje także w URL przechwycenia lub w końcowym URL, jest stosowane wszędzie indziej, tekst pozostaje w tym URL, a reguła jest wymieniana w `rules_in_capture_url` i w redaction_manifest.json (leakage_check.capture_url). Zaczerniać można tylko przechwycenia stron internetowych (porównanie, certyfikacja lub zaczernienie jest odrzucane z kodem 422 REDACTION_SOURCE_UNSUPPORTED).\n\nZaczernienie przechwycenia testowego (wykonanego testowym kluczem API, w trybie testowym (sandbox) lub przez konto bez powiązanego konta rozliczeniowego) jest pakietem testowym niezależnie od tego, który klucz go zażąda: ze znakiem wodnym, z `test_mode: true` w manifest.json, z wierszem testowym (key_env test), który nie jest naliczany i nie zużywa miesięcznej jednostki. include_eidas przy takim zaczernieniu jest odrzucane z kodem 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nRaport z zaczernienia (evidence.pdf) jest renderowany w języku `language` (domyślnie w języku przechwycenia źródłowego), łącznie z treścią, stroną tytułową i banerem testowym.\n\nZanim cokolwiek zostanie zaczernione, każdy plik, który zaczernienie czyta lub przenosi (zrzut ekranu, page.html, domtextcontent.txt, metadata.json, forensic_log.json, chain_of_custody.json, text_layout.json), a który manifest źródłowy wymienia ze skrótem SHA-256, jest ponownie haszowany (redaction_manifest.json derived_from.source_files_rehashed); niezgodność lub brak pliku zwraca 422 REDACTION_SOURCE_INTEGRITY_FAILED i nic nie jest wydawane, więc uszkodzone lub zmienione źródło nigdy nie zostanie ponownie zapieczętowane nowym podpisem. Pliki pomijane w zaczernionym pakiecie nie są czytane. Synchroniczne zaczernienie, którego źródło czytało się zbyt długo, zatrzymuje się, zanim cokolwiek zostanie zarezerwowane, z kodem 500 CAPTURE_TIME_BUDGET_EXCEEDED.\n\nZaczernienie przechwycenia objętego blokadą prawną również jest nią objęte (legal_hold_source \"derived:<capture id>\"), podobnie jak każde zaczernienie przechwycenia, na które nałożono blokadę; zdjęcie blokady prawnej z przechwycenia zdejmuje te blokady, a dopóki przechwycenie pozostaje zablokowane, nie można ich zdjąć osobno (409 LEGAL_HOLD_SOURCE). captured_at wiersza zaczernienia to czas przechwycenia jego źródła; redacted_at to chwila wykonania zaczernienia.\n\nZaczernienie działa w trybie fail-closed: gdy kontrola po zaczernieniu nadal znajdzie zaczerniony termin w dowolnym miejscu pakietu poza URL przechwycenia i końcowym URL, żaden pakiet nie jest wydawany, a żądanie zwraca 422 REDACTION_LEAK_DETECTED (`error.details` `{ files, rule_indexes, metadata_fields, in_capture_url }`; sam termin nigdy nie jest powtarzany w odpowiedzi). redaction_manifest.json zapisuje `leakage_check.fail_closed: true`.\n\nWartości są obcinane na granicach znaków (znak nigdy nie jest dzielony): ciągi dopasowania tekstu do 2000 znaków, przyczyny do 200, reason_summary do 500, pola custodian do 200. Przyczyny, reason_summary i pola custodian są drukowane na certyfikacie zaczernienia i muszą być zwykłym tekstem: znak sterujący, niewidoczny lub zmieniający kierunek tekstu jest odrzucany z kodem 400.\n\nBudżet czasu i ponowienia: żądanie zwraca odpowiedź w ramach 29-sekundowego limitu API Gateway. Pieczętowanie działa w budżecie czasu liczonym od żądania; jeśli kwalifikowany znacznik czasu (include_eidas) nie zmieściłby się w budżecie, nie jest żądany, a pakiet jest dostarczany tylko z zakotwiczeniem OpenTimestamps (timestamps.eidas status not_obtained, przyczyna time_budget, bez naliczenia), a żądanie OpenTimestamps, które się nie mieści, jest pomijane (chain_of_custody.json zapisuje ots_skipped z przyczyną time_budget). Wyślij Idempotency-Key, aby ponowienie (na przykład po 504 z bramy lub błędzie sieci) zwróciło to samo zaczernienie, zamiast tworzyć i naliczać drugie. Ponowienie w czasie, gdy pierwsze żądanie nadal trwa, zwraca 409 IDEMPOTENCY_IN_PROGRESS z identyfikatorem zaczernienia w `error.details.capture_id` (odpytuj z nim GET /captures/{capture_id}). Późniejsze ponowienie zwraca 201 z pierwotną treścią i `Idempotent-Replay: true`. Zaczernienie, które się nie powiedzie, zwalnia swój klucz, więc ponowienie z tym samym kluczem uruchamia się od nowa.\n\nLimit liczby żądań na konto w oknach 60-sekundowych (wszystkie klucze API konta i jego sesja w panelu dzielą jedno okno). Po zadziałaniu limitera odpowiedzi tej trasy zawierają nagłówki `RateLimit-Policy` i `RateLimit` (na podstawie IETF draft-ietf-httpapi-ratelimit-headers) oraz starsze `X-RateLimit-Limit`, `X-RateLimit-Remaining` i `X-RateLimit-Reset`. Odpowiedź 429 dodaje `Retry-After`.\n\nZaimportowanego pakietu dowodowego (źródło import, identyfikatory z prefiksem `imp_`) nie można zaczernić: 422 IMPORT_NOT_SUPPORTED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "boxes": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "x": {
                          "type": "number",
                          "minimum": 0
                        },
                        "y": {
                          "type": "number",
                          "minimum": 0
                        },
                        "w": {
                          "type": "number",
                          "minimum": 0
                        },
                        "h": {
                          "type": "number",
                          "minimum": 0
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200
                        }
                      },
                      "required": [
                        "x",
                        "y",
                        "w",
                        "h"
                      ]
                    },
                    "maxItems": 200,
                    "description": "Prostokąty w pikselach zrzutu ekranu przechwycenia źródłowego (x i y od jego lewego górnego rogu, w i h to szerokość i wysokość). Gdy wszystkie cztery wartości prostokąta mieszczą się między 0 a 1, są odczytywane jako ułamki szerokości i wysokości zrzutu ekranu. Prostokąt leżący całkowicie poza zrzutem ekranu niczego nie zamalowuje i jest wymieniany w ignored_boxes; prostokąt wychodzący poza krawędź jest przycinany do krawędzi i wymieniany w clipped_boxes. Gdy wszystkie prostokąty leżą poza zrzutem, a żadne zaczernienie tekstu ani metadanych niczego nie zmienia, żądanie jest odrzucane z 400 REDACTION_NO_OPS, zanim cokolwiek zostanie naliczone."
                  },
                  "text_redactions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "match": {
                          "oneOf": [
                            {
                              "type": "string",
                              "description": "Tekst dosłowny.",
                              "minLength": 1
                            },
                            {
                              "type": "object",
                              "properties": {
                                "pattern": {
                                  "type": "string",
                                  "description": "Wyrażenie regularne, od 1 do 500 znaków. Wzorce z zagnieżdżonymi kwantyfikatorami, takie jak (a+)+, są odrzucane z 400.",
                                  "minLength": 1,
                                  "maxLength": 500
                                },
                                "flags": {
                                  "type": "string",
                                  "description": "Dowolne z g, i, m, s, u, każda najwyżej raz. y (sticky) jest odrzucana z kodem 400: dopasowywałaby tylko w jednej pozycji i pomijała pozostałe wystąpienia.",
                                  "pattern": "^[gimsu]*$"
                                }
                              },
                              "required": [
                                "pattern"
                              ]
                            }
                          ]
                        },
                        "case_insensitive": {
                          "type": "boolean",
                          "default": false,
                          "description": "Dopasowuje dosłowny ciąg znaków bez względu na wielkość liter (dla wyrażenia regularnego użyj flagi i)."
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200,
                          "description": "Zwykły tekst, drukowany na certyfikacie zaczernienia."
                        }
                      },
                      "required": [
                        "match"
                      ]
                    },
                    "maxItems": 200
                  },
                  "metadata_fields": {
                    "type": "array",
                    "description": "Ścieżki pól w metadata.json (segment `*` pasuje do każdego elementu tablicy) lub nazwy `ip_dns`, `cookie_names` i `authorization` (zob. wyżej).",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "Ścieżka pola w metadata.json."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "reason": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field"
                          ]
                        }
                      ]
                    },
                    "maxItems": 100
                  },
                  "reason_summary": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Zwykły tekst, drukowany na certyfikacie zaczernienia."
                  },
                  "custodian": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string"
                      }
                    },
                    "description": "Drukowane w raporcie z zaczernienia. name i title podlegają regule zwykłego tekstu (zob. PlainText)."
                  },
                  "language": {
                    "type": "string",
                    "description": "Język plików PDF czytelnych dla człowieka. Jedna z wartości: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Wielkość liter, łącznik i region są tolerowane (\"DE\", \"fr-FR\" i \"PT-BR\" są akceptowane); samo \"pt\" i \"zh\" jest odrzucane. Nieobsługiwana wartość zwraca 400 MALFORMED_REQUEST. Opcjonalne: bez tego pola (lub gdy jest puste) raport jest w języku przechwycenia źródłowego."
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Jak w CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Adres URL https, który otrzymuje redaction.completed. Zdarzenie jest wysyłane zaraz po odpowiedzi 201, a nie przed nią. Adres URL musi używać https i nie może wskazywać na adres prywatny, loopback, link-local, carrier-grade NAT ani adres metadanych chmury (te same reguły co dla adresu URL przechwycenia; w przeciwnym razie 422 SSRF_BLOCKED). Pusta wartość lub wartość złożona wyłącznie z białych znaków oznacza brak webhooka. Wymaga klucza API: wysłany z sesji w panelu zostaje odrzucony z 400 WEBHOOK_REQUIRES_API_KEY, ponieważ webhooki są podpisywane sekretem webhooka danego klucza.",
                    "format": "uri"
                  },
                  "rule_commitment_salt": {
                    "type": "string",
                    "description": "Opcjonalne. Twoja własna 32-bajtowa sól dla zobowiązań reguł (redaction_manifest.json rule_commitments), jako 64 cyfry szesnastkowe lub base64 / base64url; sól złożona z jednego powtórzonego bajtu jest odrzucana (400). Wyślij ją, jeśli chcesz mieć sól jeszcze przed wywołaniem: bez niej sól tworzy serwer i zwraca ją tylko w odpowiedzi 201, więc odpowiedź utracona z powodu przekroczenia limitu czasu (504 z bramy, mimo że zaczernienie się zakończyło) oznacza utratę soli, a ponowienie z Idempotency-Key jej nie zawiera. ProofSnap jej nie przechowuje."
                  }
                },
                "description": "Co najmniej jedno z: boxes, text_redactions lub metadata_fields."
              },
              "example": {
                "text_redactions": [
                  {
                    "match": "jane.doe@example.com",
                    "reason": "personal data"
                  }
                ],
                "reason_summary": "Personal data of a third party"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Zaczerniony pakiet jest gotowy. Idempotentne powtórzenie wcześniejszego zaczernienia z tym samym Idempotency-Key również zwraca 201 z `Idempotent-Replay: true`: oryginalną treść z aktualnym statusem i świeżo wstępnie podpisanym package_url (pomijanym, gdy pakietu nie można wstępnie podpisać; odczytaj go przez GET /captures/{capture_id}).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "Idempotent-Replay": {
                "$ref": "#/components/headers/IdempotentReplay"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "redaction_id": {
                      "type": "string",
                      "example": "red_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "completed"
                      ]
                    },
                    "redacted_from": {
                      "type": "string"
                    },
                    "redaction_count": {
                      "type": "integer",
                      "description": "Liczba zastosowanych operacji zaczernienia."
                    },
                    "original_signature_verified": {
                      "type": "boolean",
                      "description": "Czy oryginalny podpis manifestu został pomyślnie zweryfikowany przed zaczernieniem."
                    },
                    "leakage_warnings": {
                      "type": "integer",
                      "description": "Zawsze 0: zaczernienie, w którym kontrola po zaczernieniu znajdzie pozostały termin, jest odrzucane z kodem 422 REDACTION_LEAK_DETECTED zamiast zostać wydane. Zachowane ze względu na zgodność."
                    },
                    "package_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "package_url_expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "verify_url": {
                      "type": "string",
                      "description": "Link do publicznego weryfikatora ProofSnap.",
                      "format": "uri"
                    },
                    "ignored_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Indeksy (od zera) w tablicy prostokątów z żądania, wskazujące prostokąty, które leżały całkowicie poza zrzutem ekranu i niczego nie zaczerniły. Zapisywane także w redaction_manifest.json (screenshot_boxes)."
                    },
                    "clipped_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Indeksy (od zera) w tablicy prostokątów z żądania, wskazujące prostokąty, które wychodziły poza krawędź zrzutu ekranu i zostały na niej przycięte. Zapisywane także w redaction_manifest.json (screenshot_boxes)."
                    },
                    "screenshot_method": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "jpeg_dct_blocks",
                        "decode_repaint_jpeg",
                        null
                      ],
                      "description": "Sposób zamalowania obszarów: jpeg_dct_blocks (skompresowane bloki JPEG pod każdym obszarem zastąpione czarnymi blokami, wszystkie pozostałe bloki bez zmian) lub decode_repaint_jpeg (zrzut ekranu zdekodowany, zamalowany i ponownie zakodowany). null, gdy żaden obszar niczego nie zamalował. Zapisywane także w redaction_manifest.json (screenshot_boxes.method)."
                    },
                    "painted_rects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "x": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "y": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "w": {
                            "type": "integer",
                            "minimum": 0
                          },
                          "h": {
                            "type": "integer",
                            "minimum": 0
                          }
                        },
                        "required": [
                          "x",
                          "y",
                          "w",
                          "h"
                        ]
                      },
                      "description": "Faktycznie zaczernione obszary w pikselach zrzutu ekranu, po jednym na każdy zastosowany prostokąt, w kolejności prostokątów z żądania, z pominięciem zignorowanych. Przy jpeg_dct_blocks każdy obszar to prostokąt zaokrąglony na zewnątrz do pełnych bloków, więc go zawiera; przy decode_repaint_jpeg jest to prostokąt przycięty do zrzutu ekranu i zaokrąglony na zewnątrz do pełnych bloków 8 x 8 nowego pliku, które są zaczerniane po ponownym zakodowaniu, aby kompresja nie mogła przenieść do prostokąta koloru z sąsiedztwa. Zapisywane także w redaction_manifest.json (screenshot_boxes.painted_rects)."
                    },
                    "text_rule_rects": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Odrębne obszary zrzutu ekranu zaczernione wyłącznie na potrzeby zaczernień tekstu, bez obszarów zawierających prostokąt (obszar dopasowany przez dwie reguły liczy się raz): w blokach tekstu pliku text_layout.json źródła, których tekst pasuje do reguły, szacowane wiersze zawierające dopasowanie lub cały blok, gdy oszacowanie jest niepewne. Wymienione w redaction_manifest.json (screenshot_boxes.from_text_rules)."
                    },
                    "text_occurrences_not_located": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Wystąpienia zaczernianego tekstu w tekście strony, których nie udało się zlokalizować na zrzucie ekranu i których tam nie zaczerniono (tekst, którego nie wymienia text_layout.json źródła, np. tekst wewnątrz obrazu; blok zbyt duży, by zaczerniać go z powodu jednego wyrażenia; obszary ponad limit jednego zaczernienia; zrzut ekranu, na którym nie dało się rysować). Wystąpienia w bloku leżącym całkowicie poza zrzutem ekranu (nie ma tam czego zaczernić) nie są tu liczone; redaction_manifest.json wymienia je jako occurrences_not_on_screenshot. Nigdy więcej niż wystąpień w tekście strony. Sprawdź zrzut ekranu i dodaj obszary. Liczby dla poszczególnych reguł podaje redaction_manifest.json (leakage_check.screenshot_text_advisory)."
                    },
                    "text_variants_remaining": {
                      "type": "object",
                      "description": "Informacyjne, nigdy nie powoduje błędu: dla każdej reguły tekstu dosłownego liczba wystąpień pozostałych w domtextcontent.txt tego pakietu i w tekście jego page.html, których reguła nie dopasowała, ale które pasują po pominięciu wielkości liter i znaków diakrytycznych (na przykład \"boskovické\" dla reguły uwzględniającej wielkość liter \"Boskovic\"), oraz informacja, czy termin występuje w URL przechwycenia lub w końcowym URL, które nigdy nie są zaczerniane. `total` sumuje dla każdej reguły większą z dwóch liczb (oba pliki zawierają ten sam tekst strony). Tylko liczby, nigdy tekst. Reguły regex nie są liczone. Także w redaction_manifest.json (leakage_check.text_variants_advisory). Obecne, gdy żądanie zawiera zaczernienia tekstu.",
                      "properties": {
                        "total": {
                          "type": "integer",
                          "minimum": 0
                        },
                        "per_rule": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "rule_index": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_domtextcontent": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_page_html_text": {
                                "type": "integer",
                                "minimum": 0
                              },
                              "in_capture_url": {
                                "type": "boolean"
                              }
                            }
                          }
                        }
                      }
                    },
                    "rules_in_capture_url": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Występuje, gdy żądanie zawiera zaczernienia tekstu. Indeksy (liczone od zera) reguł tekstowych, których tekst występuje także w adresie URL przechwycenia lub w końcowym adresie URL. Te adresy URL nigdy nie są zmieniane: tekst usunięto wszędzie indziej, a w nich pozostaje widoczny. Puste, gdy żadna reguła do nich nie pasuje. Także w redaction_manifest.json (leakage_check.capture_url, `remains_in_capture_url: true` dla każdej reguły) i w raporcie z zaczernienia."
                    },
                    "rule_commitment_salt": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "Występuje, gdy żądanie zawiera zaczernienia tekstu. 32-bajtowa sól (hex; rule_commitment_salt z żądania, jeśli została wysłana) dla rule_commitments w redaction_manifest.json: dla każdej reguły tekstowej SHA-256 z soli reguły, po której następują bajty UTF-8 kanonicznego JSON reguły {\"match\": <text, or {\"pattern\", \"flags\"}>, \"case_insensitive\": <bool>}, gdzie sól reguły to HMAC-SHA256 z tą solą jako kluczem, obliczony nad tekstem UTF-8 \"rule:<rule_index>\" (dzięki temu jedną regułę można wykazać, ujawniając tylko jej własną sól, bez pozostałych). Reguły nie są przechowywane, a sól jest zwracana tylko w tej odpowiedzi: nie w ponowieniu z Idempotency-Key, w webhooku, w dzienniku zdarzeń ani w GET /captures/{capture_id}, a ProofSnap jej nie przechowuje. Zachowaj ją w swojej dokumentacji: dzięki niej możesz wykazać, które reguły zastosowano przy zaczernieniu."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason; na przykład powód, który nie jest zwykłym tekstem, lub flaga y wyrażenia regularnego), REDACTION_NO_OPS (nic nie podano albo nic nie zostało dopasowane) lub INVALID_URL (webhook_url nie jest https). WEBHOOK_REQUIRES_API_KEY: webhook_url wysłany z sesji panelu (webhooki wymagają klucza API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED lub PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: to nie jest Twoje przechwycenie. EIDAS_REQUIRES_LIVE_KEY: include_eidas z testowym kluczem API lub przy zaczernianiu testowego przechwycenia (wtedy z details.reason). EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony. PLAN_ARCHIVE_ONLY: plan przechowuje tylko archiwum i nie pozwala tworzyć niczego nowego. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (w tym planie nowe elementy tworzy się wyłącznie w panelu; to miękka bariera, a nie granica bezpieczeństwa).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "REDACTION_NOT_READY: przechwycenie źródłowe nie ma statusu completed. IDEMPOTENCY_IN_PROGRESS: żądanie z tym samym Idempotency-Key jest nadal wykonywane (details.capture_id to identyfikator zaczernienia, który należy odpytywać przez GET /captures/{capture_id}). COMPANY_READ_ONLY: źródło należy do firmy przechowywanej jako archiwum firmy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "REDACTION_SCREENSHOT_UNREADABLE: dane obrazu zrzutu ekranu są nieczytelne lub zrzut ekranu przekracza limit zaczerniania prostokątami (150 megapikseli dla JPEG w postaci zapisanej przez przechwycenie, 60 megapikseli dla PNG lub innego JPEG); zaczernianie tekstu i metadanych działa również bez prostokątów. REDACTION_LEAK_DETECTED: zaczerniony termin pozostał poza URL przechwycenia (details { files, rule_indexes, metadata_fields, in_capture_url }; in_capture_url ma wartość true, gdy tekst odrzuconej reguły występuje także w URL przechwycenia lub w końcowym URL, gdzie nigdy nie jest liczony). REDACTION_SOURCE_UNSUPPORTED: źródło nie jest przechwyceniem strony internetowej (details { source } lub { manifest_type }). SSRF_BLOCKED: webhook_url wskazuje zablokowany adres. REDACTION_SOURCE_INTEGRITY_FAILED: plik pakietu źródłowego nie zgadza się ze skrótem SHA-256 podanym w jego podpisanym manifeście lub brakuje wymienionego pliku (details { mismatched, missing }, tylko nazwy plików); niczego nie zaczerniono ani nie naliczono. IDEMPOTENCY_KEY_REUSED: Idempotency-Key został już użyty z inną treścią żądania (lub z innego środowiska kluczy).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zbyt wiele żądań w bieżącym 60-sekundowym oknie. `error.details.retry_after_seconds` powtarza Retry-After. RateLimit zgłasza r=0 i liczbę sekund do zresetowania okna. RATE_LIMITED również dla sesji panelu Website Archive: jej dzienny limit 100 jednostek na dzień UTC, wspólny dla pojedynczych i wsadowych przechwyceń (jedna jednostka na URL), porównań, zaczernień i certify-hash (jedna jednostka na żądanie); żądanie, które się nie mieści, jest odrzucane w całości (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After i details.retry_after_seconds podają liczbę sekund do następnego dnia UTC, a minutowe nagłówki RateLimit i X-RateLimit-* są pomijane). Krótka odmowa zawierająca tylko details.retry_after_seconds i Retry-After: 1 oznacza równoczesną aktualizację licznika: ponów żądanie. Odmowa, która nie wynika z winy wywołującego, zwraca jednostki; jednostka zużyta na pracę przyjętą z 202, która później się nie powiedzie, nie jest zwracana. To miękka bramka, a nie granica bezpieczeństwa.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: nieoczekiwany błąd serwera; ponów próbę z wykładniczym wydłużaniem odstępów. CAPTURE_TIME_BUDGET_EXCEEDED: synchronicznemu zaczernianiu skończył się budżet czasu i zostało ono przerwane, zanim wydano jakikolwiek pakiet; nic nie zostało naliczone. Przy `details.reason` `source_read` odczyt pakietu źródłowego zajął większość budżetu: pakiet jest obecnie zbyt duży do zaczernienia, spróbuj ponownie później.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/compare": {
      "post": {
        "tags": [
          "Porównanie i zaczernianie"
        ],
        "operationId": "compareCaptures",
        "summary": "Porównanie dwóch przechwyceń",
        "description": "Porównuje dwa Twoje zakończone przechwycenia (tekst strony, obrazy wyświetlane na stronie, metadane) i tworzy podpisany pakiet porównania. Zwraca 202; odpytuj GET /captures/{compare_id}, który następnie zawiera type, compared i diff_summary. Obrazy są porównywane na podstawie spisu obrazów zapieczętowanego przez każde przechwycenie źródłowe (image_inventory.json: adres każdego wyświetlonego obrazu i skrót SHA-256 jego bajtów), nigdy piksel po pikselu; wyniki znajdują się w image_changes.json i w diff_summary.image_changes. Źródło przechwycone, zanim przechwycenia zaczęły zapisywać spis obrazów, jest nadal porównywane, ale bez porównania obrazów (diff_summary.image_changes.status not_run). Rozliczane jako jedno przechwycenie. Porównanie porównywalnych (2026-10-14): dwa przechwycenia wyrenderowane z innym profilem urządzenia lub z innymi ustawieniami przechwycenia są odrzucane z błędem 422 COMPARE_PROFILE_MISMATCH (details.differing, details.before, details.after: tylko różniące się pola, wstrzyknięty CSS i JavaScript jako ich SHA-256), chyba że allow_profile_mismatch ma wartość true, a dwa przechwycenia różnych stron z błędem 422 COMPARE_URL_MISMATCH (details.before i details.after: url i final_url jako origin i ścieżka), chyba że allow_url_mismatch ma wartość true. Kontrola jest wykonywana najpierw na dwóch rekordach przechwyceń (zanim cokolwiek zostanie odczytane lub zarezerwowane), a następnie na zapieczętowanych plikach metadata.json i forensic_log.json obu pakietów w ramach opisanej niżej kontroli źródeł; niezgodność wykryta dopiero w trakcie porównania kończy porównanie niepowodzeniem z tym kodem i zwraca jednostkę miesięcznego limitu. Tekst jest porównywany wiersz po wierszu deterministycznym algorytmem różnic wierszowych (bez limitu czasu): diff_summary liczy wiersze (text_unit line). Porównywać można tylko przechwycenia stron internetowych (wykonane przez API lub automatycznie według harmonogramu): certyfikacja skrótu, porównanie lub pakiet zaczerniony są odrzucane z błędem 422 COMPARE_SOURCE_UNSUPPORTED (`details.capture_id`, `details.source`), zanim zostanie odczytany jakikolwiek pakiet i zanim cokolwiek zostanie zarezerwowane. Przed jakimkolwiek porównaniem lub rezerwacją ponownie obliczany jest skrót SHA-256 każdego pliku, który manifest.json pakietu źródłowego wymienia ze skrótem SHA-256; źródło ze zmienionym lub brakującym plikiem albo z manifestem, który nie wymienia żadnego pliku ze skrótem SHA-256, jest odrzucane z błędem 422 COMPARE_SOURCE_INTEGRITY_FAILED (bez porównania, bez rekordu, bez zużycia jednostki miesięcznego limitu). Porównywane są tylko te wymienione pliki: plik w pakiecie źródłowym, którego manifest nie wymienia ze skrótem SHA-256 (na przykład dodany text_layout.json, image_inventory.json, page.html lub domtextcontent.txt), jest traktowany jako nieobecny. Kontrola jest wykonywana przy żądaniu, gdy oba pakiety źródłowe mają 50 MB lub mniej i kończy się w ciągu około 10 sekund; w przeciwnym razie żądanie jest przyjmowane, a porównanie powtarza kontrolę, i źródło, które jej nie przejdzie, kończy porównanie niepowodzeniem (GET /captures/{compare_id} pokazuje status failed z error.code COMPARE_SOURCE_INTEGRITY_FAILED) oraz zwraca jednostkę miesięcznego limitu. Zakończone porównanie zapisuje tę kontrolę w compare.json `sources[].source_files_reverified` i w comparison_method.json `source_verification`. /compare nie wysyła webhooków: webhook_url w treści żądania jest ignorowany. Odpytuj GET /captures/{compare_id}.\n\nPorównanie, które obejmuje przechwycenie testowe (wykonane testowym kluczem API, w trybie testowym (sandbox) lub przez konto bez powiązanego konta rozliczeniowego), jest pakietem testowym niezależnie od tego, jaki klucz o nie prosi: ze znakiem wodnym, z `test_mode: true` w manifest.json, z `test_mode: true` dla tego źródła w compare.json `sources[]` oraz z rekordem testowym (key_env test), który nie jest rozliczany i nie zużywa jednostki miesięcznego limitu. include_eidas ze źródłem testowym jest odrzucany z błędem 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nLimit liczby żądań na konto w 60-sekundowych oknach (wszystkie klucze API konta i jego sesja w panelu współdzielą jedno okno). Po zadziałaniu mechanizmu limitu odpowiedzi tej ścieżki zawierają nagłówki `RateLimit-Policy` i `RateLimit` (oparte na IETF draft-ietf-httpapi-ratelimit-headers) oraz starsze `X-RateLimit-Limit`, `X-RateLimit-Remaining` i `X-RateLimit-Reset`. Odpowiedź 429 dodaje `Retry-After`.\n\nWyślij Idempotency-Key, aby ponowienie (na przykład po 504) zwróciło pierwszą odpowiedź zamiast drugiego, rozliczanego uruchomienia (24 godziny, te same zasady co w POST /captures). Klucz jest zajmowany przed kontrolą źródeł, więc ponowienie w czasie, gdy pierwsze żądanie jest jeszcze przetwarzane, zwraca 409 IDEMPOTENCY_IN_PROGRESS; żądanie odrzucone przez kontrolę źródeł lub limit zwalnia swój klucz, więc ponowienie z tym samym kluczem jest sprawdzane od nowa.\n\nZaimportowanego pakietu dowodowego (źródło import, identyfikatory z prefiksem `imp_`) nie można porównać: 422 IMPORT_NOT_SUPPORTED (details.capture_id).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_id_1": {
                    "type": "string",
                    "description": "Wcześniejsze przechwycenie."
                  },
                  "capture_id_2": {
                    "type": "string",
                    "description": "Późniejsze przechwycenie. Musi się różnić od capture_id_1."
                  },
                  "language": {
                    "type": "string",
                    "description": "Język pliku PDF z porównaniem. Tylko dokładny kod; każda inna wartość bez ostrzeżenia powoduje użycie języka angielskiego.",
                    "enum": [
                      "en",
                      "de",
                      "it",
                      "fr",
                      "es",
                      "pt_BR",
                      "cs",
                      "el",
                      "hi",
                      "hu",
                      "id",
                      "ja",
                      "ko",
                      "nl",
                      "pl",
                      "ro",
                      "ru",
                      "sv",
                      "tr",
                      "vi",
                      "zh_CN",
                      "zh_TW",
                      "ar",
                      "he",
                      "da",
                      "fi",
                      "nb",
                      "sk",
                      "uk"
                    ]
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Jak w CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Dodaj do porównania niepodpisany szablon certyfikacji (rule_902_certification.pdf, zob. CaptureOptions.include_rule_902). Niedostępne dla przechwyceń wyrenderowanych w różny sposób: przy allow_profile_mismatch true i niezgodności profilu lub ustawień zwracany jest 422 RULE902_NOT_AVAILABLE_FOR_MISMATCH. Obecnie wyłączone: szablon certyfikacji jest tymczasowo wyłączony, więc to pole jest akceptowane i ignorowane (rule_902_certification.pdf nie jest generowany ani wymieniany w pakiecie), a odpowiedź zawiera ostrzeżenie RULE902_DISABLED.",
                    "default": false
                  },
                  "allow_profile_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Porównuje dwa przechwycenia, mimo że wykonano je z innym profilem urządzenia (options.viewport mobile lub desktop, viewport_width, viewport_height, device_scale_factor; zapieczętowane: rozmiar okna widoku, współczynnik skalowania urządzenia, emulacja urządzenia mobilnego, user agent bez numerów wersji) lub z innymi ustawieniami przechwycenia, które zmieniają sposób renderowania strony (dark_mode, block_ads, block_resources, inject_css, inject_js według SHA-256, expand_collapsed, dismiss_consent, dismiss_popups, cała strona). Bez tego parametru taka para jest odrzucana z błędem 422 COMPARE_PROFILE_MISMATCH. Z nim diff_summary.text_verdict i images_verdict mają wartość undetermined, changed ma wartość null, chyba że różni się pole metadanych klasy content, a include_rule_902 jest odrzucany."
                  },
                  "allow_url_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Porównuje dwa przechwycenia różnych stron. Dwa przechwycenia są uznawane za tę samą stronę, gdy adres jednego z nich (żądany url oraz url i final_url zapisane w jego metadata.json) jest po normalizacji równy jednemu z adresów drugiego (host małymi literami, http i https traktowane tak samo, z pominięciem domyślnego portu, fragmentu, końcowego ukośnika i zmiennych parametrów zapytania). Bez tego parametru różne strony są odrzucane z błędem 422 COMPARE_URL_MISMATCH."
                  }
                },
                "required": [
                  "capture_id_1",
                  "capture_id_2"
                ]
              },
              "example": {
                "capture_id_1": "cap_a1B2c3D4e5F6g7H8",
                "capture_id_2": "cap_h8G7f6E5d4C3b2A1"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Przyjęte.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "X-Quota-Limit": {
                "$ref": "#/components/headers/QuotaLimit"
              },
              "X-Quota-Remaining": {
                "$ref": "#/components/headers/QuotaRemaining"
              },
              "X-Quota-Used": {
                "$ref": "#/components/headers/QuotaUsed"
              },
              "X-Quota-Cap": {
                "$ref": "#/components/headers/QuotaCap"
              },
              "X-Quota-Org-Limit": {
                "$ref": "#/components/headers/QuotaOrgLimit"
              },
              "X-Quota-Org-Used": {
                "$ref": "#/components/headers/QuotaOrgUsed"
              },
              "X-Sandbox-Limit": {
                "$ref": "#/components/headers/SandboxLimit"
              },
              "X-Sandbox-Remaining": {
                "$ref": "#/components/headers/SandboxRemaining"
              },
              "X-Sandbox-Used": {
                "$ref": "#/components/headers/SandboxUsed"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "compare_id": {
                      "type": "string",
                      "example": "cmp_a1B2c3D4e5F6g7H8"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string"
                    },
                    "message": {
                      "type": "string"
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: nieprawidłowy JSON, brak ID, to samo ID dwa razy albo allow_profile_mismatch / allow_url_mismatch, które nie jest wartością logiczną (boolean).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "QUOTA_EXCEEDED lub PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "FORBIDDEN: przechwycenie nie należy do Ciebie. EIDAS_REQUIRES_LIVE_KEY: include_eidas z testowym kluczem API lub z testowym przechwyceniem źródłowym (wtedy z details.reason). EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony. PLAN_ARCHIVE_ONLY: plan przechowuje tylko archiwum i nie pozwala tworzyć niczego nowego. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (w tym planie nowe elementy tworzy się wyłącznie w panelu; to miękka bariera, a nie granica bezpieczeństwa).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "409": {
            "description": "COMPARE_NOT_READY: oba przechwycenia muszą być ukończone. COMPANY_READ_ONLY: źródło należy do firmy utrzymywanej jako archiwum firmy (nie tworzy się z niego nic nowego).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "COMPARE_SOURCE_INTEGRITY_FAILED: plik przechwycenia źródłowego nie odpowiada skrótowi SHA-256 wymienionemu w jego podpisanym manifeście lub go brakuje; niczego nie porównano ani nie naliczono. `details.capture_id`, `details.mismatched`, `details.missing` wskazują pierwsze wadliwe źródło; `details.reason` występuje, gdy sam manifest źródłowy nie nadaje się do użycia (na przykład \"the source manifest lists no file with a SHA-256\" lub \"the source manifest lists no files\"); `details.sources[]` wymienia każde wadliwe źródło z tymi samymi polami. Zweryfikuj przechwycenie źródłowe (POST /captures/{capture_id}/verify). COMPARE_SOURCE_UNSUPPORTED: źródło nie jest przechwyceniem strony internetowej (certyfikacja skrótu, porównanie lub zaczerniony pakiet); niczego nie przeczytano, nie porównano ani nie naliczono. `details.capture_id` wskazuje to źródło, a `details.source` jego rodzaj, jeśli wiersz go zapisuje. Porównaj zamiast tego oryginalne przechwycenia stron. COMPARE_PROFILE_MISMATCH: oba przechwycenia wykonano z innym profilem urządzenia lub innymi ustawieniami przechwytywania; `details.differing` wymienia nazwy pól (poziom wiersza: viewport, viewport_width, viewport_height, device_scale_factor, dark_mode, block_ads, block_resources, inject_css, inject_js, expand_collapsed, dismiss_consent, dismiss_popups, full_page; poziom zapieczętowany: viewport_width, viewport_height, device_scale_factor, is_mobile, user_agent i te same ustawienia), a `details.before` i `details.after` ich wartości. Wyślij allow_profile_mismatch: true, aby mimo to porównać. COMPARE_URL_MISMATCH: przechwycenia dotyczą różnych stron; `details.before` i `details.after` podają url i final_url jako origin i ścieżkę. Wyślij allow_url_mismatch: true, aby mimo to porównać. RULE902_NOT_AVAILABLE_FOR_MISMATCH: include_rule_902 z allow_profile_mismatch dla przechwyceń wyrenderowanych w różny sposób; `details.differing`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zbyt wiele żądań w bieżącym 60-sekundowym oknie. `error.details.retry_after_seconds` powtarza Retry-After. RateLimit zgłasza r=0 i liczbę sekund do zresetowania okna. RATE_LIMITED również dla sesji panelu Website Archive: jej dzienny limit 100 jednostek na dzień UTC, wspólny dla pojedynczych i wsadowych przechwyceń (jedna jednostka na URL), porównań, zaczernień i certify-hash (jedna jednostka na żądanie); żądanie, które się nie mieści, jest odrzucane w całości (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After i details.retry_after_seconds podają liczbę sekund do następnego dnia UTC, a minutowe nagłówki RateLimit i X-RateLimit-* są pomijane). Krótka odmowa zawierająca tylko details.retry_after_seconds i Retry-After: 1 oznacza równoczesną aktualizację licznika: ponów żądanie. Odmowa, która nie wynika z winy wywołującego, zwraca jednostki; jednostka zużyta na pracę przyjętą z 202, która później się nie powiedzie, nie jest zwracana. To miękka bramka, a nie granica bezpieczeństwa.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/certify-hash": {
      "post": {
        "tags": [
          "Certyfikacja skrótu"
        ],
        "operationId": "certifyHash",
        "summary": "Certyfikacja skrótów plików",
        "description": "Wyślij jeden skrót SHA-256 (hash) lub do 100 skrótów (hashes). Pakiet zawiera manifest.json (z Twoimi skrótami), manifest.sig, publickey.pem, evidence.pdf, skrypty verification/ oraz, jeśli jest dostępny, manifest.json.ots.\n\n- Jeden skrót: synchronicznie, zwraca 200 z gotowym certyfikatem.\n- Kilka skrótów z combined: true: jeden certyfikat obejmujący wszystkie skróty, synchronicznie, zwraca 200.\n- Kilka skrótów bez combined: jeden niezależny certyfikat na każdy skrót, uruchamiany w tle; zwraca 202 z certify_ids. Odpytuj GET /captures/{certify_id}; z webhook_url każdy z nich wysyła certify.completed.\n\nKażdy certyfikat liczy się jako jedno przechwycenie przy pomiarze zużycia, miesięcznym limicie i limicie liczby żądań (N skrótów bez combined liczy się jako N; N większe niż limit okna zwraca 400 RATE_LIMIT_COST_TOO_HIGH). `label` jest drukowany na certyfikacie: jest przycinany do 200 znaków i musi być zwykłym tekstem (bez znaków sterujących, niewidocznych ani znaków kierunku tekstu, w przeciwnym razie 400). W partii błąd dotyczący jednej pozycji wskazuje ją w `error.details.index`.\n\nLimit liczby żądań obowiązuje na konto w 60-sekundowych oknach (wszystkie klucze API konta i jego sesja panelu współdzielą jedno okno). Po zadziałaniu mechanizmu limitu odpowiedzi tego endpointu zawierają nagłówki `RateLimit-Policy` i `RateLimit` (oparte na projekcie IETF draft-ietf-httpapi-ratelimit-headers) oraz starsze `X-RateLimit-Limit`, `X-RateLimit-Remaining` i `X-RateLimit-Reset`. Odpowiedź 429 dodaje `Retry-After`.\n\nWyślij Idempotency-Key, aby ponowienie (na przykład po 504) zwróciło pierwszą odpowiedź zamiast drugiego, rozliczanego uruchomienia (24 godziny, te same zasady co dla POST /captures).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 jako 64 znaki szesnastkowe (wielkość liter nie ma znaczenia). Użyj hash lub hashes."
                  },
                  "label": {
                    "type": "string",
                    "description": "Nazwa wyświetlana dla pliku. Dłuższe wartości są skracane do 200 znaków."
                  },
                  "hashes": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "SHA-256 w postaci szesnastkowej."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "hash": {
                              "type": "string"
                            },
                            "label": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "hash"
                          ]
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "combined": {
                    "type": "boolean",
                    "description": "Certyfikuj wszystkie skróty w jednym certyfikacie.",
                    "default": false
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "sha256"
                    ],
                    "default": "sha256"
                  },
                  "language": {
                    "type": "string",
                    "description": "Język plików PDF czytelnych dla człowieka. Jedna z wartości: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Wielkość liter, łącznik i region są tolerowane (\"DE\", \"fr-FR\" i \"PT-BR\" są akceptowane); samo \"pt\" i \"zh\" jest odrzucane. Nieobsługiwana wartość zwraca 400 MALFORMED_REQUEST."
                  },
                  "case_reference": {
                    "type": "string",
                    "description": "Drukowane na certyfikacie. Podlega regule zwykłego tekstu (zob. PlainText); maksymalnie 200 znaków.",
                    "maxLength": 200
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Jak w CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Dodaj niepodpisany szablon certyfikacji (rule_902_certification.pdf, zob. CaptureOptions.include_rule_902). Obecnie wyłączone: szablon certyfikacji jest tymczasowo wyłączony, więc to pole jest akceptowane i ignorowane (rule_902_certification.pdf nie jest generowany ani wymieniany w pakiecie), a odpowiedź zawiera ostrzeżenie RULE902_DISABLED.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Adres URL https, który otrzymuje certify.completed. Adres URL musi używać https i nie może wskazywać na adres prywatny, loopback, link-local, carrier-grade NAT ani adres metadanych chmury (te same reguły co dla adresu URL przechwycenia; w przeciwnym razie 422 SSRF_BLOCKED). Pusta wartość lub wartość złożona wyłącznie z białych znaków oznacza brak webhooka. Wymaga klucza API: wysłany z sesji w panelu zostaje odrzucony z 400 WEBHOOK_REQUIRES_API_KEY, ponieważ webhooki są podpisywane sekretem webhooka danego klucza.",
                    "format": "uri"
                  }
                }
              },
              "example": {
                "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "label": "contract.pdf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Certyfikat gotowy (jeden skrót lub łączny).",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CertifyResult"
                }
              }
            }
          },
          "202": {
            "description": "Uruchomiono kilka niezależnych certyfikatów. Certyfikat, który po uruchomieniu zostanie odrzucony przez miesięczny limit lub pulę trybu testowego (sandbox), kończy się niepowodzeniem: jego wiersz (GET /captures/{certify_id}) ma status failed i error.code QUOTA_EXCEEDED.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "batch_id": {
                      "type": "string"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "certify_ids": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      },
                      "description": "Identyfikatory utworzonych certyfikatów (łącznie z tymi, których uruchomienie się nie powiodło)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "certificates": {
                      "type": "array",
                      "description": "Jedna pozycja na każdy przesłany skrót, w kolejności przesłania. status failed: nie udało się rozpocząć tego certyfikatu (jego wiersz, jeśli powstał, jest oznaczony jako failed); pozostałe są przetwarzane dalej.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "certify_id": {
                            "type": "string",
                            "nullable": true
                          },
                          "hash_sha256": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string",
                            "enum": [
                              "processing",
                              "failed"
                            ]
                          }
                        }
                      }
                    },
                    "failed_count": {
                      "type": "integer",
                      "description": "Liczba pozycji certificates, których nie udało się uruchomić. Gdy nie uda się uruchomić żadnej, odpowiedzią jest zamiast tego 500 CONFIGURATION_ERROR."
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason wskazuje pole, na przykład \"label must be a string\"; details.index wskazuje pozycję partii), INVALID_HASH (details.index w partii) lub INVALID_URL (webhook_url nie jest https albo zawiera dane uwierzytelniające). WEBHOOK_REQUIRES_API_KEY: webhook_url wysłany z sesji panelu (webhooki wymagają klucza API). RATE_LIMIT_COST_TOO_HIGH: więcej skrótów (bez combined), niż pozwala jedno okno limitu liczby żądań; details { max_per_window, window_seconds, requested }.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED lub QUOTA_EXCEEDED na ścieżkach synchronicznych.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "403": {
            "description": "EIDAS_REQUIRES_LIVE_KEY (details.reason \"no_billing_account\", gdy produkcyjny klucz API lub sesja działa w trybie testowym, ponieważ nie powiązano konta rozliczeniowego). EMAIL_NOT_VERIFIED: sesja w panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta. PLAN_ARCHIVE_ONLY: plan jedynie przechowuje archiwum i nie tworzy nowej pracy. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja w panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (nowa praca w tym planie jest tworzona wyłącznie w panelu; łagodna bariera, a nie granica bezpieczeństwa).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: webhook_url wskazuje adres prywatny, pętli zwrotnej (loopback), link-local lub w inny sposób zablokowany.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: zbyt wiele żądań w bieżącym 60-sekundowym oknie. `error.details.retry_after_seconds` powtarza Retry-After. RateLimit zgłasza r=0 i liczbę sekund do zresetowania okna. RATE_LIMITED również dla sesji panelu Website Archive: jej dzienny limit 100 jednostek na dzień UTC, wspólny dla pojedynczych i wsadowych przechwyceń (jedna jednostka na URL), porównań, zaczernień i certify-hash (jedna jednostka na żądanie); żądanie, które się nie mieści, jest odrzucane w całości (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After i details.retry_after_seconds podają liczbę sekund do następnego dnia UTC, a minutowe nagłówki RateLimit i X-RateLimit-* są pomijane). Krótka odmowa zawierająca tylko details.retry_after_seconds i Retry-After: 1 oznacza równoczesną aktualizację licznika: ponów żądanie. Odmowa, która nie wynika z winy wywołującego, zwraca jednostki; jednostka zużyta na pracę przyjętą z 202, która później się nie powiedzie, nie jest zwracana. To miękka bramka, a nie granica bezpieczeństwa.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "RATE_LIMITED",
                    "message": "Too many requests. See Retry-After header.",
                    "details": {
                      "retry_after_seconds": 42
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: certyfikacja nie powiodła się (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: synchroniczna certyfikacja (jednego skrótu lub łączona) nie zakończyła się w ciągu około 25 s. Może jeszcze się zakończyć: details.certify_id i details.poll_path wskazują wiersz, który należy odpytywać przez GET /captures/{capture_id}.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimitPolicy"
              },
              "RateLimit": {
                "$ref": "#/components/headers/RateLimit"
              },
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "X-RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            }
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/webhook-events/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "listWebhookEvents",
        "summary": "Dziennik dostarczeń webhooków",
        "description": "Każda próba dostarczenia dla jednego przechwycenia, certyfikatu lub zaczernienia, od najstarszej.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Dziennik dostarczeń.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "webhook_url": {
                      "type": "string",
                      "description": "Adres URL webhooka wiersza (options.webhook_url przechwycenia albo adres URL podany przy certyfikacie lub zaczernieniu; null, gdy go nie ma). Właściciel firmy, dla której wykonano przechwycenie, odczytując dziennik miejsca, widzi tylko schemat, host, port i ścieżkę (zapytanie i fragment są usuwane, ponieważ mogą zawierać token); ten odczyt jest zapisywany w historii dostępu do przechwycenia (action webhook_events).",
                      "nullable": true
                    },
                    "attempts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "attempt_number": {
                            "type": "integer"
                          },
                          "attempted_at": {
                            "type": "string",
                            "format": "date-time"
                          },
                          "status_code": {
                            "type": "integer",
                            "nullable": true
                          },
                          "latency_ms": {
                            "type": "integer",
                            "nullable": true
                          },
                          "success": {
                            "type": "boolean"
                          },
                          "error": {
                            "type": "string",
                            "nullable": true,
                            "description": "null, gdy dostarczono. W przeciwnym razie ogólna klasa, nigdy surowy błąd sieciowy: connection_failed, timeout, blocked_address (w chwili dostarczenia host został rozwiązany na adres prywatny lub w inny sposób zablokowany), redirect_not_followed (endpoint odpowiedział kodem 3xx; przekierowania nigdy nie są wykonywane), http_<status> (odpowiedź spoza zakresu 2xx) albo zdanie wyjaśniające, dlaczego zdarzenie nie zostało wysłane (brak klucza API, klucz unieważniony lub nieznaleziony, brak sekretu webhooka, odczyt sekretu nie powiódł się)."
                          },
                          "delivery_id": {
                            "type": "string",
                            "description": "Nagłówek webhook-id tego dostarczenia bez prefiksu `msg_`."
                          }
                        }
                      }
                    },
                    "attempt_count": {
                      "type": "integer"
                    },
                    "last_success": {
                      "type": "string",
                      "description": "attempted_at ostatniej udanej próby.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: to nie jest Twoje przechwycenie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures": {
      "post": {
        "tags": [
          "Automatyczne przechwycenia"
        ],
        "operationId": "createSchedule",
        "summary": "Utworzenie automatycznego przechwycenia",
        "description": "interval i cron: każde uruchomienie wykonuje przechwycenie i, jeśli podano webhook_url, wysyła capture.completed lub capture.failed.\n\non_change: każde uruchomienie najpierw pobiera stronę (zwykłym HTTP albo renderując ją w przeglądarce z js_render) i oblicza skrót jej znormalizowanego tekstu. Ta kontrola nie jest przechwyceniem i nie jest rozliczana. Pierwsze uruchomienie wykonuje przechwycenie bazowe. Gdy skrót się zmieni, wykonywane jest nowe przechwycenie, porównywane z poprzednim, a na webhook_url wysyłane jest monitor.change_detected. Strona, która zwraca błąd lub prawie nie zawiera tekstu, jest traktowana jako niezmieniona.\n\nPo 5 kolejnych nieudanych uruchomieniach automatyczne przechwycenie samo się wstrzymuje. Gdy osiągnięto miesięczny limit lub płatność za subskrypcję jest zaległa, uruchomienia są pomijane, a automatyczne przechwycenie pozostaje aktywne.\n\nLimity według planu:\n\n| Plan | Aktywne automatyczne przechwycenia | Minimalny odstęp | on_change |\n| --- | --- | --- | --- |\n| sandbox | 0 | niedostępne | nie |\n| developer | 3 | 300 s | tak |\n| startup | 25 | 300 s | tak |\n| growth | 200 | 300 s | tak |\n| custom | bez limitu | 300 s | tak |\n| legal_solo | 3 | 300 s | tak |\n| legal_seats | 3 | 300 s | tak |\n| website_archive | 30 | 86400 s | tak |\n| archive_only | 0 | niedostępne | nie |\n\nIdempotency-Key: ponowienie z tym samym kluczem i treścią w ciągu 24 godzin otrzymuje z powrotem pierwszą odpowiedź 201 (nagłówek Idempotent-Replay: true) zamiast drugiego automatycznego przechwycenia; ten sam klucz z inną treścią zwraca 422 IDEMPOTENCY_KEY_REUSED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Utworzono.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST, INVALID_URL, INVALID_CRON, INTERVAL_TOO_SHORT (details.min_interval_seconds) lub błąd opcji przechwycenia. WEBHOOK_REQUIRES_API_KEY: webhook_url wysłany z sesji panelu (webhooki wymagają klucza API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED (details.max_active, details.active) lub ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony. PLAN_ARCHIVE_ONLY: plan przechowuje tylko archiwum i nie tworzy nowych zadań. PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (nowe zadania w tym planie tworzy się wyłącznie w panelu; to miękka bariera, a nie granica bezpieczeństwa).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url lub webhook_url wskazuje adres prywatny, pętli zwrotnej (loopback), link-local lub w inny sposób zablokowany.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      },
      "get": {
        "tags": [
          "Automatyczne przechwycenia"
        ],
        "operationId": "listSchedules",
        "summary": "Lista automatycznych przechwyceń",
        "description": "Do 100 Twoich automatycznych przechwyceń.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Automatyczne przechwycenia.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedules": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Schedule"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "Automatyczne przechwycenia"
        ],
        "operationId": "getSchedule",
        "summary": "Pobranie automatycznego przechwycenia",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Automatyczne przechwycenie.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Automatyczne przechwycenia"
        ],
        "operationId": "updateSchedule",
        "summary": "Wstrzymanie, wznowienie lub edycja automatycznego przechwycenia",
        "description": "Wyślij status, aby wstrzymać lub wznowić, i/lub dowolne pole ScheduleRequest, aby je edytować. Wysłane pole najwyższego poziomu zastępuje zapisane, także jawne null (w ten sposób czyści się webhook_url, timezone i js_render); pominięte pole zachowuje zapisaną wartość; wynik jest ponownie walidowany jako całość. `options` jest wyjątkiem od edycji pole po polu: jest zastępowane jako cała mapa, a nie scalane klucz po kluczu, więc wyślij kompletne opcje, których chcesz (najpierw odczytaj je przez GET). Opcja pominięta w mapie zostaje usunięta. Wznowienie sprawdza limit aktywnych automatycznych przechwyceń, zeruje licznik niepowodzeń i oblicza następne uruchomienie od chwili obecnej.\n\nZ sesji panelu nie można ustawić webhook_url (400 WEBHOOK_REQUIRES_API_KEY); wysłanie null lub pustego ciągu go czyści. Automatyczne przechwycenie utworzone z sesji panelu nigdy nie miało klucza API, którym można by podpisywać, więc jego edycja usuwa także zapisany wcześniej webhook_url, a klucz API również nie może go dodać (400 WEBHOOK_REQUIRES_API_KEY): planista podpisuje webhooki automatycznego przechwycenia sekretem webhooka klucza, którym je utworzono. Aby otrzymywać webhooki, utwórz automatyczne przechwycenie kluczem API.\n\nZmiana url, mode lub js_render automatycznego przechwycenia on_change (albo przełączenie automatycznego przechwycenia na on_change lub z on_change) czyści jego stan odniesienia (zapisany skrót sondy i ostatnie przechwycenie), więc następne uruchomienie wykonuje nowe przechwycenie odniesienia zamiast porównywać nowy cel ze starym. Treść żądania musi być obiektem JSON (w przeciwnym razie 400 MALFORMED_REQUEST).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "active",
                      "paused"
                    ]
                  },
                  "url": {
                    "type": "string",
                    "format": "uri"
                  },
                  "mode": {
                    "type": "string",
                    "enum": [
                      "interval",
                      "cron",
                      "on_change"
                    ]
                  },
                  "interval_seconds": {
                    "type": "integer"
                  },
                  "cron_expr": {
                    "type": "string"
                  },
                  "timezone": {
                    "type": "string",
                    "nullable": true,
                    "description": "Strefa czasowa IANA dla cron. null lub pusty ciąg znaków przywraca UTC."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Wyślij null lub pusty ciąg, aby go usunąć. URL musi używać https i nie może wskazywać adresu prywatnego, pętli zwrotnej (loopback), link-local, carrier-grade NAT ani adresu metadanych chmury (te same zasady co dla URL przechwycenia; w przeciwnym razie 422 SSRF_BLOCKED). Wymaga klucza API po obu stronach: wysłany z sesji panelu albo wysłany z kluczem API dla automatycznego przechwycenia utworzonego w panelu jest odrzucany z 400 WEBHOOK_REQUIRES_API_KEY, ponieważ webhooki są podpisywane sekretem webhooka klucza, którym utworzono automatyczne przechwycenie.",
                    "nullable": true
                  },
                  "js_render": {
                    "type": "boolean",
                    "nullable": true,
                    "description": "null oznacza false."
                  },
                  "options": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CaptureOptions"
                      }
                    ],
                    "description": "Zastępuje zapisane opcje w całości (nie scala ich klucz po kluczu). Wyślij kompletną mapę; pominięta opcja zostaje usunięta. null przywraca opcje domyślne. options.webhook_url nigdy nie jest zapisywane (zob. webhook_url)."
                  },
                  "max_capture_age_seconds": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 86400,
                    "maximum": 34560000,
                    "description": "Tylko dla on_change: pełne przechwycenie co najmniej co N sekund, nawet bez zmiany (od 1 do 400 dni, dłużej niż interval_seconds). null usuwa to ustawienie. Przełączenie trybu na interval lub cron je usuwa."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Zaktualizowane automatyczne przechwycenie.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (także gdy nie ma nic do zaktualizowania), INVALID_URL, INVALID_CRON lub INTERVAL_TOO_SHORT. WEBHOOK_REQUIRES_API_KEY: webhook_url wysłany z sesji panelu albo wysłany z kluczem API dla automatycznego przechwycenia utworzonego w panelu (nie ma ono klucza, którego sekret webhooka mógłby podpisywać webhooki).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED lub ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony (wznowienie wstrzymanego automatycznego przechwycenia; wstrzymywanie i edycja pozostają dostępne). PLAN_API_READ_ONLY: klucz API konta Website Archive, przy każdym PATCH (w tym wstrzymywaniu i edycji). PLAN_API_READ_ONLY z details.reason \"session_outside_dashboard\": sesja panelu konta Website Archive, której żądanie nie ma nagłówka Origin albo ma taki, który nie jest adresem panelu ProofSnap (nowe zadania w tym planie tworzy się wyłącznie w panelu; to miękka bariera, a nie granica bezpieczeństwa). PLAN_ARCHIVE_ONLY: wznowienie automatycznego przechwycenia w planie Archive only.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url lub webhook_url wskazuje adres prywatny, pętli zwrotnej (loopback), link-local lub w inny sposób zablokowany.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Automatyczne przechwycenia"
        ],
        "operationId": "deleteSchedule",
        "summary": "Usunięcie automatycznego przechwycenia",
        "description": "Zatrzymuje przyszłe uruchomienia. Wykonane już przechwycenia są zachowywane.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Usunięto.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "PLAN_API_READ_ONLY: klucz API konta Website Archive (jego klucz jest tylko do odczytu; użyj panelu).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/scheduled-captures/{schedule_id}/runs": {
      "parameters": [
        {
          "$ref": "#/components/parameters/ScheduleId"
        }
      ],
      "get": {
        "tags": [
          "Automatyczne przechwycenia"
        ],
        "operationId": "listScheduleRuns",
        "summary": "Lista uruchomień automatycznego przechwycenia",
        "description": "50 najnowszych przechwyceń wykonanych przez to automatyczne przechwycenie (sprawdzenia on_change, które nie wykryły zmiany, nie są wymieniane).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Uruchomienia.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "schedule_id": {
                      "type": "string"
                    },
                    "runs": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "status": {
                            "type": "string"
                          },
                          "created_at": {
                            "type": "string"
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true
                          },
                          "file_count": {
                            "type": "integer",
                            "nullable": true
                          }
                        }
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters": {
      "get": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "listMatters",
        "summary": "Lista spraw",
        "description": "Do 200 spraw, których jesteś członkiem. Sprawy firmy, którą opuściłeś, nie są wymieniane.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sprawy.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matters": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Matter"
                      }
                    },
                    "total": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "createMatter",
        "summary": "Utworzenie sprawy",
        "description": "Stajesz się jej właścicielem. Dziennik łańcucha dowodowego zaczyna się od wpisu matter.created. Pola tekstowe są przycinane z białych znaków na brzegach i skracane do 200 znaków.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). Wymagane. Wyświetlany na publicznej stronie weryfikacji sprawy."
                  },
                  "client": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). Wartość pusta lub null oznacza brak ustawienia (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana.",
                    "nullable": true
                  },
                  "case_number": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). Wartość pusta lub null oznacza brak ustawienia (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana. Wyświetlany na publicznej stronie weryfikacji sprawy.",
                    "nullable": true
                  },
                  "opposing_party": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). Wartość pusta lub null oznacza brak ustawienia (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana.",
                    "nullable": true
                  },
                  "jurisdiction": {
                    "type": "string",
                    "description": "Dowolny tekst. Zwykły tekst: białe znaki są scalane i przycinane, maksymalnie 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (dozwolone są łączniki U+200C i U+200D, których wymaga pisownia hindi i perska) (w przeciwnym razie 400 MALFORMED_REQUEST). Pusta wartość lub null oznacza brak ustawienia (zapisywane jako null). Wartość, która nie jest ciągiem znaków, jest odrzucana. Wyświetlane na publicznej stronie weryfikacji sprawy.",
                    "maxLength": 200,
                    "nullable": true
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Smith v. Jones",
                "case_number": "2026-CV-0142"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Utworzono.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MISSING_FIELD: brak name lub name jest puste. MALFORMED_REQUEST: nieprawidłowy JSON. MALFORMED_REQUEST: pole tekstowe, które nie jest ciągiem znaków, jest dłuższe niż 200 znaków lub zawiera znaki sterujące albo znaki kierunku tekstu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal. EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "get": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "getMatter",
        "summary": "Pobranie sprawy",
        "description": "Sprawa wraz z jej przechwyceniami (do 500), dziennikiem łańcucha dowodowego i maksymalnie 100 zdarzeniami dostępu. `members` podaje adres e-mail każdego wpisu `member_uids`, a `actor_emails` przypisuje identyfikatorom uid wymienionym w dzienniku łańcucha dowodowego i audit_stream tej sprawy (aktorzy oraz cel `member.added` / `member.removed`, a więc także usunięci członkowie) ich adres e-mail; adres e-mail ma wartość null albo uid brakuje w mapie, gdy nie można go odczytać. Te pola otrzymują tylko wywołujący, którzy mogą otworzyć sprawę; `/matter-verify` nigdy nie zawiera adresów e-mail. Wywołujący, który może unieważnić publiczny link do udostępniania (właściciel sprawy, właściciel zakończonej lub zarchiwizowanej firmy), otrzymuje też `share`: `{ share_url, created_at, expires_at }` aktywnego linku (`expires_at` ma zawsze wartość null: linki działają, dopóki nie zostaną unieważnione) albo null, gdy takiego linku nie ma; pozostali członkowie nigdy nie otrzymują tego pola. Wpisy audit_stream zawierają source_ip i user_agent tylko dla właściciela sprawy; członkowie otrzymują wpisy bez tych dwóch pól.\n\nSprawa firmy, która zakończyła działalność, ma `read_only: true`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Sprawa.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Matter"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "members": {
                          "type": "array",
                          "description": "Jedna pozycja na każdą pozycję `member_uids`, w tej samej kolejności.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "uid": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "nullable": true,
                                "description": "Null, gdy nie można odczytać konta."
                              }
                            }
                          }
                        },
                        "actor_emails": {
                          "type": "object",
                          "description": "`{ uid: email }` dla osób wymienionych w custody_log i audit_stream tej sprawy (z wyłączeniem aktora system). uid, którego adresu e-mail nie można odczytać, jest pomijany.",
                          "additionalProperties": {
                            "type": "string",
                            "format": "email"
                          }
                        },
                        "captures": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MatterCapture"
                          }
                        },
                        "custody_log": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CustodyEntry"
                          }
                        },
                        "audit_stream": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AuditEntry"
                          }
                        },
                        "share": {
                          "type": "object",
                          "nullable": true,
                          "description": "Tylko dla wywołującego, który może unieważnić link. Null, gdy nie istnieje aktywny link.",
                          "properties": {
                            "share_url": {
                              "type": "string",
                              "format": "uri"
                            },
                            "created_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true
                            },
                            "expires_at": {
                              "type": "string",
                              "format": "date-time",
                              "nullable": true,
                              "description": "Zawsze null: linki do udostępniania nie wygasają."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "updateMatter",
        "summary": "Aktualizacja sprawy",
        "description": "Tylko dla właściciela. Pola tekstowe są przycinane i skracane do 200 znaków. name jest wymagane: jeśli zostanie wysłane, musi być niepustym ciągiem znaków, a pusta nazwa, nazwa z samych białych znaków, null lub wartość niebędąca ciągiem znaków jest odrzucana z 400 i nic nie jest zmieniane. Członkowie: add_member_email dodaje osobę, która ma już konto ProofSnap i należy do firmy, do której należy sprawa ; brak konta, niepotwierdzony adres i konto spoza firmy dostają jedną i tę samą odpowiedź 404 NOT_FOUND; remove_member_uid usuwa członka (nie właściciela). Gdy wysłane są oba pola, stosowane jest tylko add_member_email. Sprawa ma najwyżej 50 członków (pełna sprawa odrzuca żądanie, zanim adres zostanie wyszukany). Zmiany członków są zapisywane w dzienniku łańcucha dowodowego. Zmiana name, client, case_number, opposing_party, jurisdiction lub status jest zapisywana w dzienniku łańcucha dowodowego jako matter.updated; jego target to ciąg JSON z fields (nazwy zmienionych pól) oraz, gdy zmienił się status, z nowym status. Wartości tekstu dowolnego nie są zapisywane w objętym skrótem, publicznym dzienniku łańcucha dowodowego: stare i nowe wartości trafiają do dziennika dostępu (audit_stream, wpis matter.updated). W zarchiwizowanej sprawie zmiana nazwy lub add_member_email jest odrzucana z 409 MATTER_ARCHIVED, chyba że to samo żądanie ją ponownie otwiera (status \"open\"); remove_member_uid i ponowne otwarcie nadal działają. Każde zaproszenie wlicza się do dziennego limitu 50 na zapraszającego właściciela (zaproszenia do firmy z panelu i add_member_email w sprawach łącznie, wliczając odmowy, ponieważ każde z nich wyszukuje adres); po osiągnięciu limitu żądanie zwraca 429 RATE_LIMITED. Błąd po stronie serwera nie jest liczony.\n\nBlokada prawna: samo `{ \"legal_hold\": true|false }` ustawia blokadę prawną sprawy (tylko właściciel sprawy) i w tym samym żądaniu stosuje ją do jej przechwyceń, ze skutkami blokady przechwycenia (blokada prawna S3 Object Lock, wpisy łańcucha dowodowego capture.legal_hold_placed / capture.legal_hold_released, łańcuch zdarzeń przechwyceń). Odpowiedzią jest sprawa oraz `changed` (flaga się zmieniła), `captures_held` lub `captures_released`, `skipped` ([{ capture_id, code }], np. przechwycenie w koszu) i `truncated` (ponad 1000 przechwyceń: wyślij ponownie tę samą wartość, aby kontynuować). Przechwycenia przypisane do sprawy później, gdy blokada trwa, są obejmowane blokadą w chwili przypisania. Jedna jednostka limitu liczby żądań na żądanie plus jedna na każde 10 zmienionych przechwyceń.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Musi zawierać co najmniej jeden znak inny niż biały. Pomiń je, aby zachować obecną nazwę. Obowiązuje ta sama reguła zwykłego tekstu: białe znaki są scalane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu i znaków niewidocznych (U+200C i U+200D są dozwolone).",
                    "maxLength": 200
                  },
                  "client": {
                    "type": "string",
                    "nullable": true,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). null lub ciąg złożony z samych białych znaków czyści pole (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana z 400.",
                    "maxLength": 200
                  },
                  "case_number": {
                    "type": "string",
                    "nullable": true,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). null lub ciąg złożony z samych białych znaków czyści pole (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana z 400.",
                    "maxLength": 200
                  },
                  "opposing_party": {
                    "type": "string",
                    "nullable": true,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). null lub ciąg złożony z samych białych znaków czyści pole (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana z 400.",
                    "maxLength": 200
                  },
                  "jurisdiction": {
                    "type": "string",
                    "nullable": true,
                    "description": "Zwykły tekst: białe znaki są scalane i przycinane, najwyżej 200 znaków, bez znaków sterujących, znaków kierunku tekstu (bidi) i znaków niewidocznych (łączniki U+200C i U+200D, których wymaga pisownia hindi i perska, są dozwolone) (w przeciwnym razie 400 MALFORMED_REQUEST). null lub ciąg złożony z samych białych znaków czyści pole (zapisywane jako null). Wartość niebędąca ciągiem znaków jest odrzucana z 400.",
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "description": "Aby zapieczętować, użyj POST /matters/{matter_id}/seal.",
                    "enum": [
                      "open",
                      "archived"
                    ]
                  },
                  "add_member_email": {
                    "type": "string",
                    "format": "email"
                  },
                  "remove_member_uid": {
                    "type": "string"
                  },
                  "legal_hold": {
                    "type": "boolean",
                    "description": "Blokada prawna sprawy, wysyłana samodzielnie (razem z innym polem: 400). true obejmuje blokadą każde bieżące przechwycenie sprawy i każde przechwycenie przypisane do niej, dopóki blokada trwa (legal_hold_source \"matter:<id>\"; przechwycenie, które ma już blokadę, zachowuje własną). false zdejmuje tylko blokady nałożone przez sprawę; blokada ręczna lub zbiorcza pozostaje. Tylko dla właściciela sprawy. Zaległa płatność subskrypcji tego nie blokuje. Nałożenie wymaga płatnego planu (403 LEGAL_HOLD_NOT_AVAILABLE, details.reason plan), a przechwycenia testowe są pomijane (kod LEGAL_HOLD_NOT_AVAILABLE w `skipped`); samo false jest przyjmowane także po zakończeniu planu Legal."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Zaktualizowana sprawa.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: name jest puste, zawiera same białe znaki lub nie jest ciągiem znaków; pole tekstowe, które nie jest ciągiem znaków, jest dłuższe niż 200 znaków lub zawiera znaki sterujące albo znaki kierunku tekstu; nieprawidłowy status; brak zmian do zapisania; użytkownik jest już członkiem; albo próba usunięcia właściciela.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (nie jesteś właścicielem), LEGAL_HOLD_NOT_AVAILABLE (legal_hold true bez płatnego planu) lub EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND lub NOT_FOUND (brak uprawnionego konta dla adresu e-mail: konto nie istnieje, nie jest potwierdzone lub jest spoza firmy, we wszystkich przypadkach ta sama odpowiedź; albo remove_member_uid nie jest członkiem).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED lub MATTER_ARCHIVED (zmiana nazwy lub nowy członek w zarchiwizowanej sprawie). COMPANY_READ_ONLY: sprawa należy do firmy, która zakończyła działalność lub jest przechowywana jako archiwum firmy (wywołujący ma plan Legal; otwieranie, eksport i unieważnienie udostępnienia nadal działają).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: osiągnięto dzienny limit 50 zaproszeń członków na jednego zapraszającego właściciela (zaproszenia do firmy z panelu i add_member_email w sprawie liczą się łącznie; details.daily_limit, details.action \"member_invite\"). Retry-After podaje, kiedy kończy się dzień UTC.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/captures": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "assignMatterCaptures",
        "summary": "Dodawanie lub usuwanie przechwyceń",
        "description": "Może to wywołać każdy członek, ale przenoszone są tylko przechwycenia utworzone przez wywołującego; pozostałe są zwracane w skipped, podobnie jak przechwycenia należące już do innej sprawy oraz przechwycenia, które już są w tej sprawie (lub, przy unassign, których w niej nie ma). Wpisy niebędące identyfikatorami przechwyceń są ignorowane, a przetwarzanych jest najwyżej 100. Każda zmiana jest zapisywana w dzienniku łańcucha dowodowego. Zarchiwizowana sprawa nie przyjmuje nowych przechwyceń (409 MATTER_ARCHIVED); unassign nadal działa.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "capture_ids": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "unassign": {
                    "type": "boolean",
                    "description": "true usuwa przechwycenia ze sprawy.",
                    "default": false
                  }
                },
                "required": [
                  "capture_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wynik.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "assigned": {
                      "type": "integer",
                      "description": "Liczba zmienionych przechwyceń."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "unassign": {
                      "type": "boolean"
                    },
                    "legal_holds_placed": {
                      "type": "integer",
                      "description": "Występuje, gdy blokada prawna sprawy jest włączona: na ilu przypisanych przechwyceniach nałożyła blokadę."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: brak prawidłowego ID przechwycenia.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED lub EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED lub MATTER_ARCHIVED (dodawanie do zarchiwizowanej sprawy). COMPANY_READ_ONLY: sprawa należy do firmy, która zakończyła działalność lub jest przechowywana jako archiwum firmy (wywołujący ma plan Legal; otwieranie, eksport i unieważnienie udostępnienia nadal działają).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/seal": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "sealMatter",
        "summary": "Zapieczętowanie sprawy",
        "description": "Tylko dla właściciela. Zakotwicza bieżący skrót ostatniego ogniwa łańcucha dowodowego za pomocą OpenTimestamps oraz, gdy wdrożenie ma skonfigurowany eIDAS, a wywołujący działa produkcyjnie (produkcyjny klucz API lub sesja panelu z podłączonymi rozliczeniami), znacznikiem czasu eIDAS (znacznik czasu eIDAS pieczęci nie jest rozliczany; jeśli się nie powiedzie, pieczętowanie odbywa się bez niego). Wywołujący testowy (testowy klucz API lub sesja w trybie testowym) jest pieczętowany tylko za pomocą OpenTimestamps, a pieczęć zapisuje eidas_status not_obtained z eidas_reason test_key; żądanie nie ma żadnej opcji eIDAS, więc nie jest to błąd. Sprawa staje się następnie tylko do odczytu: bez edycji, bez zmian przechwyceń, a jej przechwyceń nie można usunąć. Sprawa, która nadal ma przechwycenie w koszu, nie zostanie zapieczętowana (409 MATTER_HAS_TRASHED_CAPTURES): najpierw przywróć przechwycenie albo usuń je ze sprawy.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Zapieczętowano.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "sealed"
                      ]
                    },
                    "seal": {
                      "$ref": "#/components/schemas/MatterSeal"
                    },
                    "custody_head_hash": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: sprawa nie ma ostatniego ogniwa łańcucha dowodowego.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal. FORBIDDEN: nie jesteś właścicielem. EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED (już zapieczętowana), MATTER_CHANGED (dziennik łańcucha dowodowego zmienił się podczas pieczętowania; ponów próbę) lub MATTER_HAS_TRASHED_CAPTURES (jedno z przechwyceń sprawy jest w koszu; najpierw je przywróć albo usuń ze sprawy). COMPANY_READ_ONLY: sprawa należy do firmy, która zakończyła działalność lub jest przechowywana jako archiwum firmy (wywołujący ma plan Legal; otwieranie, eksport i unieważnienie udostępnienia nadal działają).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "TIMESTAMP_SERVICE_UNAVAILABLE (zakotwiczenie nie powiodło się, nic nie zostało zmienione; ponów) lub CONFIGURATION_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "EIDAS_SEAL_RETRY: znacznik czasu eIDAS dla pieczęci nie nadszedł na czas (lub jest wciąż wydawany). Nic nie zostało zapieczętowane. Ponów próbę po liczbie sekund z Retry-After: ponowienie otrzyma znacznik czasu już wydany dla tego ostatniego ogniwa łańcucha, bez drugiego znacznika. SERVICE_UNAVAILABLE: brama nie mogła sprawdzić poświadczenia; ponów próbę z narastającym odstępem.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              },
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                },
                "example": {
                  "error": {
                    "code": "EIDAS_SEAL_RETRY",
                    "message": "The eIDAS timestamp of the seal did not arrive in time. Nothing was sealed. Retry after the Retry-After interval; the retry reuses the timestamp already issued for this custody head.",
                    "details": {
                      "reason": "The eIDAS timestamp did not arrive in time. Nothing was sealed. Retry the seal.",
                      "retry_after_seconds": 15
                    }
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "/matters/{matter_id}/export": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "exportMatter",
        "summary": "Eksport sprawy",
        "description": "Tylko dla właściciela. Tworzy jeden plik ZIP z custody_report.pdf, custody_log.json, matter.json, README.txt, access_log.json (gdy istnieją zdarzenia dostępu) oraz pakietem dowodowym każdego przechwycenia. Przy włączonej numeracji Bates (domyślnie) dodaje w exhibits/ kopie każdego evidence.pdf z numeracją Bates (dowody numerowane od najstarszego) oraz, z formats.loadfile, plik ładowania Concordance/Opticon. Z formats.pdfa kopie do złożenia w sądzie są dodatkowo konwertowane do PDF/A, gdy konwerter jest dostępny (przy eksporcie synchronicznym tylko w ramach budżetu czasu); PDF/A są tylko kopie uwzględnione w pdfa_count, pozostałe to pliki PDF, które nie deklarują zgodności z PDF/A, a README.txt wspomina o PDF/A tylko wtedy, gdy pdfa_count jest większe od 0.\n\nSprawa z maksymalnie 20 przechwyceniami jest eksportowana synchronicznie (200). Większa sprawa jest eksportowana w tle: odpowiedź to 202 z job_id; odpytuj GET /matters/{matter_id}/export?job_id=. Przechwycenia, które nie mieszczą się w limicie rozmiaru (120 MB synchronicznie; 800 MB i 300 przechwyceń w tle), są wymienione w raportach, ale nie są dołączane do pakietu.\n\nDziała dla spraw otwartych i zapieczętowanych. Eksport zarchiwizowanej sprawy jest odrzucany z 409 MATTER_ARCHIVED (najpierw otwórz ją ponownie), z wyjątkiem wywołującego, który nie może jej ponownie otworzyć: właściciela firmy, która zakończyła działalność, w okresie przechowywania, oraz konta Archive only. Każdy eksport jest zapisywany w dzienniku dostępu jako export.generated. Każdy eksport wlicza się do dziennego limitu 50 na użytkownika; eksport w tle, który się nie powiedzie, zwraca swoją jednostkę.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "bates": {
                    "type": "object",
                    "properties": {
                      "enabled": {
                        "type": "boolean",
                        "default": true
                      },
                      "prefix": {
                        "type": "string",
                        "description": "Tylko litery i cyfry, wielkimi literami, maksymalnie 12. Domyślnie: na podstawie sygnatury akt lub nazwy sprawy."
                      },
                      "start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "exhibit_start": {
                        "type": "integer",
                        "minimum": 1,
                        "default": 1
                      },
                      "pad": {
                        "type": "integer",
                        "minimum": 4,
                        "maximum": 8,
                        "default": 6
                      }
                    }
                  },
                  "formats": {
                    "type": "object",
                    "properties": {
                      "loadfile": {
                        "type": "boolean",
                        "description": "Dołącza plik ładowania e-discovery (load file; wymaga numeracji Bates).",
                        "default": true
                      },
                      "pdfa": {
                        "type": "boolean",
                        "description": "Konwertuje kopie do złożenia w sądzie opatrzone numeracją Bates na PDF/A (wymaga numeracji Bates). W miarę możliwości: kopia, której nie udało się przekonwertować, pozostaje plikiem PDF bez deklaracji PDF/A; pdfa_count w odpowiedzi podaje, ile kopii przekonwertowano.",
                        "default": true
                      }
                    }
                  },
                  "export_locale": {
                    "type": "string",
                    "description": "Język custody_report.pdf, indeksu załączników i README.txt: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk (zamiast podkreślnika akceptowany jest też łącznik). Inne wartości powodują użycie języka angielskiego."
                  },
                  "declaration": {
                    "type": "object",
                    "description": "Akceptowane ze względu na zgodność. Pola tekstowe (name, title, party, case_caption, executed_location) podlegają regule zwykłego tekstu (zob. PlainText): wartość z niedozwolonym znakiem jest odrzucana z kodem 400 MALFORMED_REQUEST. Zbyt długie wartości są skracane (name, title, executed_location do 120, party do 160, case_caption do 200 znaków)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Eksport gotowy.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "description": "Wstępnie podpisany link do pobrania, ważny 24 godziny.",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "capture_count": {
                      "type": "integer"
                    },
                    "captures_included": {
                      "type": "integer"
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "description": "Przechwycenia wymienione w raportach, ale niedołączone, ponieważ osiągnięto limit rozmiaru."
                    },
                    "custody_events": {
                      "type": "integer"
                    },
                    "exhibits_count": {
                      "type": "integer",
                      "description": "Kopie załączników z numeracją Bates."
                    },
                    "bates_prefix": {
                      "type": "string",
                      "nullable": true
                    },
                    "searchable": {
                      "type": "boolean",
                      "description": "Zawsze false: do kopii do złożenia w sądzie nie jest dodawana warstwa tekstowa. Zachowane dla stałego kształtu odpowiedzi."
                    },
                    "pdfa_count": {
                      "type": "integer",
                      "description": "Kopie do złożenia w katalogu exhibits/, które są w formacie PDF/A. 0 oznacza, że żadna kopia nie jest w formacie PDF/A i żadna się za taką nie podaje."
                    },
                    "loadfile": {
                      "type": "boolean"
                    },
                    "size_bytes": {
                      "type": "integer"
                    },
                    "generated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Eksport uruchomiony w tle.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "async": {
                      "type": "boolean"
                    },
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "description": "Zawiera etap /v1."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: jedna z opcji eksportu jest nieprawidłowa (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal. FORBIDDEN: nie jesteś właścicielem. EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: najpierw ponownie otwórz sprawę (PATCH status \"open\").",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: nie udało się zbudować ani uruchomić eksportu.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: synchroniczny eksport nie zakończył się w ciągu około 25 s (details.reason). Ponów żądanie.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "getMatterExport",
        "summary": "Odpytywanie eksportu w tle",
        "description": "Tylko dla właściciela. Zadanie, które jest nadal przetwarzane 30 minut po uruchomieniu, jest zgłaszane jako failed z błędem \"export timed out\". Zadanie kończy się tylko raz: zadanie zgłoszone jako failed nigdy później nie zmieni się na ready, a zadanie w stanie ready nigdy nie zostanie zgłoszone jako failed.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status zadania. Gdy zadanie jest gotowe, wypełniane są url i liczniki.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "job_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed"
                      ]
                    },
                    "url": {
                      "type": "string",
                      "nullable": true
                    },
                    "captures_included": {
                      "type": "integer",
                      "nullable": true
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "nullable": true
                    },
                    "custody_events": {
                      "type": "integer",
                      "nullable": true
                    },
                    "size_bytes": {
                      "type": "integer",
                      "nullable": true
                    },
                    "error": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: brak job_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal. FORBIDDEN: nie jesteś właścicielem.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND lub NOT_FOUND dla nieznanego zadania.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/matters/{matter_id}/share": {
      "parameters": [
        {
          "$ref": "#/components/parameters/MatterId"
        }
      ],
      "post": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "shareMatter",
        "summary": "Utworzenie publicznego linku weryfikacyjnego",
        "description": "Tylko dla właściciela. Zwraca link, który każdy może otworzyć, aby zobaczyć nazwę sprawy, sygnaturę akt, jurysdykcję, status, dziennik łańcucha dowodowego i pieczęć (zob. GET /matter-verify). Zawartość przechwyceń nie jest udostępniana. Ponowne wywołanie zwraca ten sam link. Link działa, dopóki go nie unieważnisz.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Link do udostępniania.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "share_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "token": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: plan nie jest planem Legal. FORBIDDEN: nie jesteś właścicielem. EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_ARCHIVED: zarchiwizowanej sprawy nie można udostępnić. COMPANY_READ_ONLY: sprawa należy do firmy, która zakończyła działalność lub jest przechowywana jako archiwum firmy (wywołujący ma plan Legal; otwieranie, eksport i unieważnienie udostępnienia nadal działają).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Sprawy"
        ],
        "operationId": "revokeMatterShare",
        "summary": "Unieważnienie publicznego linku",
        "description": "Tylko dla właściciela. Link przestaje działać. Późniejsze żądanie POST tworzy nowy. Właściciel firmy, która zakończyła działalność (lub jest przechowywana jako archiwum firmy), oraz właściciel sprawy na planie Archive only mogą unieważnić link bez planu Legal (tworzenie linku pozostaje funkcją planu Legal).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Unieważniono.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: produkcyjny wywołujący, którego subskrypcja (lub firma) ma zaległą płatność albo jest nieopłacona."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (nie jesteś właścicielem) lub EMAIL_NOT_VERIFIED: sesja panelu, której adres e-mail nie został potwierdzony, albo klucz API takiego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "Publiczne"
        ],
        "operationId": "health",
        "summary": "Kontrola stanu",
        "description": "Zawsze 200, dopóki API odpowiada. Przy deep=1 (lub true) odpowiedź zawiera dodatkowo dependencies: czy każda usługa zależna jest skonfigurowana, a dla eIDAS wynik testu usługi znacznika czasu na żywo. Testowany jest tylko eIDAS; pozostałe usługi raportują wyłącznie konfigurację. status zmienia się na degraded, gdy eIDAS jest skonfigurowany, a jego test się nie powiedzie. Test, który nie odpowie na czas, jest raportowany w dependencies (probe_timeout lub eidas.error timeout), nigdy jako nieudane /health. deep=1 ma limit liczby żądań na adres IP klienta (10 na minutę): po jego przekroczeniu żądanie zwraca 429 RATE_LIMITED z Retry-After.",
        "security": [],
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Stan.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "status": {
                      "type": "string",
                      "enum": [
                        "ok",
                        "degraded"
                      ]
                    },
                    "stage": {
                      "type": "string"
                    },
                    "timestamp": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "uptime_ms": {
                      "type": "integer",
                      "description": "Milisekundy od uruchomienia obsługującej instancji."
                    },
                    "version": {
                      "type": "string"
                    },
                    "dependencies": {
                      "type": "object",
                      "properties": {
                        "eidas": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            },
                            "healthy": {
                              "type": "boolean",
                              "description": "null, gdy nie skonfigurowano.",
                              "nullable": true
                            },
                            "error": {
                              "type": "string",
                              "description": "Stały kod, gdy test eIDAS nie zgłosił poprawnego działania: timeout (brak odpowiedzi w ciągu 15 s), unavailable (usługa znacznika czasu odpowiedziała, że nie działa poprawnie), error (nie udało się uruchomić testu). Szczegóły są zapisywane w logach po stronie serwera i nigdy nie są zwracane.",
                              "enum": [
                                "timeout",
                                "unavailable",
                                "error"
                              ]
                            }
                          }
                        },
                        "ots": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "c2pa_sign": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "ntp": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "whois": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            }
                          }
                        },
                        "error": {
                          "type": "string",
                          "description": "Tylko gdy zawiodło samo sprawdzenie zależności: probe_timeout, gdy nie odpowiedziało w ciągu 20 s, w przeciwnym razie probe_failed. Odpowiedź ma mimo to kod 200."
                        }
                      },
                      "description": "Tylko z deep=1."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ledger/{hash}": {
      "get": {
        "tags": [
          "Publiczne"
        ],
        "operationId": "ledgerLookup",
        "summary": "Wyszukanie skrótu manifestu",
        "description": "Sprawdza, czy ProofSnap odnotował wydanie pakietu, którego manifest.json ma ten skrót SHA-256. Przechwycenia, certyfikaty, porównania i zaczernienia są odnotowywane przy wydaniu, ze skrótem dostarczanego manifestu (po kroku kwalifikowanego znacznika czasu); pakietów wydanych przed powstaniem rejestru w nim nie ma. issued: false oznacza jedynie, że brak wpisu.",
        "security": [],
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "SHA-256 pliku manifest.json, 64 znaki szesnastkowe, dowolna wielkość liter, opcjonalnie z prefiksem sha256:.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Wynik wyszukiwania.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "manifest_sha256": {
                      "type": "string"
                    },
                    "issued": {
                      "type": "boolean"
                    },
                    "issued_at": {
                      "type": "string",
                      "nullable": true,
                      "format": "date-time",
                      "description": "Brak, gdy issued ma wartość false."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "capture",
                        "certify",
                        "compare",
                        "redaction"
                      ],
                      "nullable": true,
                      "description": "Brak, gdy issued ma wartość false."
                    },
                    "eidas": {
                      "type": "boolean",
                      "nullable": true,
                      "description": "Czy wydany pakiet zawiera kwalifikowany znacznik czasu eIDAS (manifest.json.tsr). Brak, gdy issued ma wartość false; null dla rekordów zapisanych przed 2026-10-01."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: skrót nie ma 64 znaków szesnastkowych.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matter-verify": {
      "get": {
        "tags": [
          "Publiczne"
        ],
        "operationId": "matterVerify",
        "summary": "Publiczny widok udostępnionej sprawy",
        "description": "Dane tylko do odczytu dostępne przez link do udostępniania sprawy: szczegóły sprawy, dziennik łańcucha dowodowego (aby można było ponownie obliczyć łańcuch skrótów) oraz pieczęć z dowodem OpenTimestamps i tokenem eIDAS. Bez treści przechwyceń, linków do pobrania ani dziennika dostępu. attested_by.organization to ustawiona przez właściciela nazwa firmy, do której należy sprawa (sprawa firmowa), lub null; sprawa osobista nie wskazuje żadnej organizacji. custody_log[].actor_org to firma, do której należał wykonawca w chwili zapisania danego wpisu (zapisywana razem z wpisem, poza danymi objętymi skrótem), tylko w sprawie firmowej; wpisy zapisane, zanim zaczęto to rejestrować, mają wartość null. Limit liczby żądań obowiązuje na adres IP klienta (domyślnie 30 żądań na 60 sekund).",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Widok sprawy.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter": {
                      "type": "object",
                      "properties": {
                        "name": {
                          "type": "string"
                        },
                        "case_number": {
                          "type": "string",
                          "nullable": true
                        },
                        "jurisdiction": {
                          "type": "string",
                          "nullable": true
                        },
                        "status": {
                          "type": "string"
                        },
                        "sealed": {
                          "type": "boolean"
                        },
                        "attested_by": {
                          "type": "object",
                          "properties": {
                            "organization": {
                              "type": "string",
                              "nullable": true
                            }
                          }
                        },
                        "capture_count": {
                          "type": "integer"
                        },
                        "custody_count": {
                          "type": "integer"
                        },
                        "custody_head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "seal": {
                          "type": "object",
                          "properties": {
                            "root_hash": {
                              "type": "string"
                            },
                            "ots_present": {
                              "type": "boolean"
                            },
                            "eidas_present": {
                              "type": "boolean"
                            },
                            "ots_hex": {
                              "type": "string",
                              "nullable": true
                            },
                            "ots_pending": {
                              "type": "boolean"
                            },
                            "tsr_base64": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_serial": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_issued_at": {
                              "type": "string",
                              "nullable": true
                            },
                            "eidas_ltv": {
                              "type": "boolean"
                            },
                            "sealed_at": {
                              "type": "string"
                            },
                            "eidas_status": {
                              "type": "string",
                              "enum": [
                                "confirmed",
                                "not_obtained",
                                "not_configured"
                              ],
                              "nullable": true,
                              "description": "null dla zapieczętowań wykonanych przed 2026-10-01 bez tokenu eIDAS."
                            },
                            "eidas_reason": {
                              "type": "string",
                              "enum": [
                                "token_refused",
                                "unavailable",
                                "test_key",
                                "account_limit"
                              ],
                              "description": "Tylko gdy eidas_status ma wartość not_obtained. test_key: pieczęć utworzył wywołujący testowy, więc nie zażądano znacznika czasu eIDAS. account_limit: konto nie ma jeszcze opłaconej faktury i osiągnęło swój limit eIDAS (20 na dobę UTC, 50 na miesiąc UTC), więc nie zażądano znacznika czasu eIDAS."
                            },
                            "eidas_verification_status": {
                              "type": "string",
                              "nullable": true,
                              "description": "verified, gdy token przeszedł kontrolę ProofSnap przy zapieczętowaniu."
                            },
                            "eidas_revocation_status": {
                              "type": "string",
                              "nullable": true,
                              "enum": [
                                "good",
                                "revoked_after_gen_time",
                                "unknown",
                                "not_checked",
                                null
                              ],
                              "description": "Status certyfikatu TSA ustalony przy pieczętowaniu (nie jest pobierany na bieżąco)."
                            },
                            "eidas_validation": {
                              "type": "object",
                              "nullable": true,
                              "additionalProperties": true,
                              "description": "Zawartość eidas_validation.json zapisana przy zapieczętowaniu (łańcuch TSA, przypięty certyfikat główny, odpowiedź OCSP, CRL, zapis weryfikacji), aby token w tsr_base64 można było sprawdzić offline. null przy starszych zapieczętowaniach."
                            }
                          },
                          "nullable": true
                        },
                        "created_at": {
                          "type": "string"
                        }
                      }
                    },
                    "custody_log": {
                      "type": "array",
                      "items": {
                        "allOf": [
                          {
                            "$ref": "#/components/schemas/CustodyEntry"
                          },
                          {
                            "type": "object",
                            "properties": {
                              "actor_org": {
                                "type": "string",
                                "nullable": true
                              }
                            }
                          }
                        ]
                      }
                    },
                    "issuer": {
                      "type": "string",
                      "enum": [
                        "ProofSnap"
                      ]
                    },
                    "verified_at": {
                      "type": "string",
                      "nullable": true,
                      "description": "Zawsze null."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: nieprawidłowy, nieznany lub unieważniony token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/captures/export-jobs": {
      "post": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "startCaptureExportJob",
        "summary": "Eksport wszystkich przechwyceń jako jednego pliku do pobrania (zadanie asynchroniczne)",
        "description": "Uruchamia zadanie, które pakuje wszystkie Twoje przechwycenia do jednego pobrania, podzielonego na części o rozmiarze maksymalnie około 2 GB. Każda część to zwykły ZIP: README.txt (po angielsku) oraz README.<language>.txt (ten sam tekst w tłumaczeniu, gdy language nie jest en; oba w UTF-8 ze znacznikiem kolejności bajtów i końcami wierszy CRLF; wiersz 1 każdego z nich odsyła do drugiego), captures.csv (UTF-8 ze znacznikiem kolejności bajtów, CRLF; każde przechwycenie eksportu z kolumnami member (e-mail autora w eksporcie firmowym, pusty w Twoim własnym eksporcie), capture_id, created_at (kiedy żądanie zostało odebrane), captured_at (moment przechwycenia; pusty dla nieudanego przechwycenia), url, status (completed, failed lub unavailable), export_part, file, ots_file (captures/<capture_id>.upgraded.ots, gdy ta część zawiera dowód, w przeciwnym razie pusty), test_mode (true dla przechwycenia wykonanego testowym kluczem API: ze znakiem wodnym, nie stanowi dowodu) i note (dlaczego pakiet nie został dołączony, także dla każdego wiersza unavailable); ta sama lista jest w każdej części, a gdy części się różnią, ostateczna jest lista z części o najwyższym numerze) oraz captures/<capture_id>.zip, oryginalny zapieczętowany pakiet dowodowy bajt po bajcie, a także captures/<capture_id>.upgraded.ots, gdy przechowywany jest zaktualizowany dowód OpenTimestamps. Każdy wewnętrzny pakiet weryfikuje się osobno za pomocą jego verification/verify.sh lub verify.ps1. Pakiet większy niż 2 GB nie jest dołączany (jest wymieniony jako unavailable z notatką; pobierz go osobno przez GET /captures/{capture_id}). Nieudane przechwycenia są wymienione tylko w captures.csv; przechwycenia w koszu i wymazane nie są eksportowane. Wpisy każdej części mają datę eksportu (UTC, także jako rozszerzone pole znacznika czasu). Odpytuj GET /captures/export-jobs/{job_id}, aż status będzie ready. **Ponowne użycie:** gdy Twój ostatni eksport jest gotowy, ma ten sam zakres (i org) oraz language, zostało mu co najmniej jeden dzień okna pobierania, Twoje uprawnienia się nie zmieniły i od jego zaplanowania żadne przechwycenie nie zostało dodane, ukończone, przeniesione do kosza, przywrócone ani wymazane, żądanie zwraca 200 z tym zadaniem (reused: true): nic nie jest budowane od nowa i nie jest zużywana żadna jednostka dzienna. Nowszy gotowy eksport o tym samym zakresie zastępuje poprzedni (poprzednie zadanie przechodzi w stan expired, expired_reason superseded). Kto może je uruchomić: produkcyjny klucz API lub sesja panelu, w każdym planie, w tym w trybie testowym (sandbox), Archive only i z kluczami tylko do odczytu (Website Archive). Testowy klucz API otrzymuje 403 FORBIDDEN (eksport zawiera przechwycenia produkcyjne), a GET /captures/export-jobs/{job_id} zwraca testowemu kluczowi API 404 EXPORT_JOB_NOT_FOUND dla każdego zadania. Twój własny eksport (scope own) zawiera Twoje przechwycenia osobiste oraz te, które wykonałeś dla swojej obecnej firmy lub firmy, której jesteś właścicielem, także przez klucz, dopóki ta firma jest działającym archiwum firmowym (przechwycenia pozostałych użytkowników miejsc w archiwum są dostępne tylko przez sesję panelu). scope company (tylko właściciel firmy, także w przypadku zakończonej firmy w jej oknie pobierania; działające archiwum firmowe tylko przez sesję panelu) eksportuje przechwycenia wszystkich użytkowników miejsc wykonane dla firmy oraz Twoje własne, z e-mailem autora w kolumnie member. Jedno działające zadanie na konto (409 EXPORT_JOB_RUNNING je wskazuje), 5 uruchomionych zadań na konto i dzień UTC (nieudane zadanie zwraca swoją jednostkę, ponownie użyte nie zużywa żadnej), minutowy limit liczby żądań Twojego planu. Maksymalnie 20 000 najnowszych przechwyceń na zadanie (truncated).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "scope": {
                    "type": "string",
                    "enum": [
                      "own",
                      "company"
                    ],
                    "default": "own"
                  },
                  "org": {
                    "type": "string",
                    "description": "Ze scope company: jedna z firm, których jesteś właścicielem (bieżąca lub ostatnia), gdy jesteś właścicielem obu."
                  },
                  "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Język tłumaczenia README: README.<language>.txt (pełne tłumaczenie) obok angielskiego README.txt w każdej części; en daje tylko README.txt. Jedna z wartości: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Wielkość liter, łącznik i region są tolerowane (\"PT-BR\" to pt_BR); samo \"pt\" i \"zh\" oraz wszystko inne zwraca 400 MALFORMED_REQUEST. Gotowy eksport jest ponownie używany tylko dla tego samego języka. Panel wysyła język swojego interfejsu."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Twój gotowy eksport jest nadal aktualny i zostanie użyty ponownie: bez nowego zadania i bez zużycia jednostki dziennej. Jego linki odczytasz przez GET poll_path.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path",
                    "reused"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "ready"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "reused": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Zadanie zostało uruchomione.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "poll_path"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "poll_path": {
                      "type": "string",
                      "example": "/v1/captures/export-jobs/cexp_3Fh8sKq0aZr2LmN1"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: treść nie jest obiektem JSON albo scope / org jest nieprawidłowe.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: żądanie wysłano z testowym kluczem API albo ze scope company przez kogoś, kto nie jest właścicielem firmy w jej oknie czasowym na pobranie danych.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "EXPORT_JOB_RUNNING: zadanie tego konta jest wciąż przetwarzane; wskazują je details.job_id i details.poll_path.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/export-jobs/{job_id}": {
      "get": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "getCaptureExportJob",
        "summary": "Status i linki do pobrania zadania eksportu przechwyceń",
        "description": "Status i postęp zadania; gdy jest gotowe, po jednym wstępnie podpisanym linku na każdą część. Link jest ważny do url_expires_at: najwyżej 24 godziny, nigdy dłużej niż do download_expires_at i nigdy dłużej niż poświadczenia, które go podpisały (może to być mniej niż godzina); po url_expires_at odpytaj ponownie, aby otrzymać nowe linki. download_expires_at przypada 7 dni po północy UTC poprzedzającej uruchomienie pierwszej części zadania (6 do 7 dni po tym, jak zadanie stało się gotowe); potem części są usuwane. Zadanie traci możliwość pobrania przed czasem (status expired, części usunięte, expired_reason), gdy zastąpił je nowszy eksport o tym samym zakresie, zawarte w nim przechwycenie zostało trwale usunięte, dane jego firmy zostały usunięte lub Twoje uprawnienia już go nie obejmują (sprawdzane przy każdym odczycie gotowego zadania). Zadanie, które przez 30 minut nie zgłasza postępu, jest przez to wywołanie kończone jako failed, a to, co zapisało, jest usuwane. Pierwsza odpowiedź z linkami w danej godzinie jest zapisywana w dzienniku audytu (capture.export_job.links_issued). Zadanie widzi tylko konto, które je uruchomiło; w przypadku eksportu firmy zachowanej jako archiwum firmowe tylko przez sesję w panelu.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cexp_[A-Za-z0-9]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Zadanie.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "job_id",
                    "status",
                    "scope",
                    "parts"
                  ],
                  "properties": {
                    "job_id": {
                      "type": "string",
                      "example": "cexp_3Fh8sKq0aZr2LmN1",
                      "pattern": "^cexp_[A-Za-z0-9]{16}$"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing",
                        "ready",
                        "failed",
                        "expired"
                      ],
                      "description": "processing w trakcie zapisywania planu i części; ready z linkami do pobrania; failed (uruchom nowe zadanie, dzienna jednostka została zwrócona); expired, gdy minęło okno pobierania lub pobieranie zostało wycofane (expired_reason; części są usuwane)."
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "language": {
                      "type": "string",
                      "enum": [
                        "en",
                        "de",
                        "it",
                        "fr",
                        "es",
                        "pt_BR",
                        "cs",
                        "el",
                        "hi",
                        "hu",
                        "id",
                        "ja",
                        "ko",
                        "nl",
                        "pl",
                        "ro",
                        "ru",
                        "sv",
                        "tr",
                        "vi",
                        "zh_CN",
                        "zh_TW",
                        "ar",
                        "he",
                        "da",
                        "fi",
                        "nb",
                        "sk",
                        "uk"
                      ],
                      "description": "Język pliku README zadania (README.<language>.txt obok README.txt; en tylko dla README.txt)."
                    },
                    "created_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "ended_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true
                    },
                    "progress": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "captures_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "captures_done": {
                          "type": "integer"
                        },
                        "parts_total": {
                          "type": "integer",
                          "nullable": true
                        },
                        "parts_done": {
                          "type": "integer"
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true, gdy konto ma więcej niż 20 000 przechwyceń możliwych do wyeksportowania: zadanie obejmuje najnowsze 20 000."
                    },
                    "counts": {
                      "type": "object",
                      "nullable": true,
                      "description": "Ustawiane, gdy zadanie jest gotowe.",
                      "properties": {
                        "captures": {
                          "type": "integer",
                          "description": "Wiersze pliku captures.csv."
                        },
                        "packaged": {
                          "type": "integer",
                          "description": "Pakiety w częściach."
                        },
                        "failed": {
                          "type": "integer",
                          "description": "Nieudane przechwycenia (bez pakietu, wymienione tylko w captures.csv)."
                        },
                        "unavailable": {
                          "type": "integer",
                          "description": "Pakiety niedołączone: nie dało się ich odczytać podczas eksportu albo są większe niż 2 GB (które to są, podaje captures.csv)."
                        }
                      }
                    },
                    "parts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "part",
                          "bytes",
                          "count",
                          "download_url"
                        ],
                        "properties": {
                          "part": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 1,
                            "description": "Numer części, od 1 do liczby części."
                          },
                          "bytes": {
                            "type": "integer",
                            "example": 1834201112,
                            "description": "Rozmiar części ZIP w bajtach."
                          },
                          "count": {
                            "type": "integer",
                            "example": 412,
                            "description": "Pakiety przechwyceń w tej części."
                          },
                          "filename": {
                            "type": "string",
                            "example": "proofsnap-captures-2026-10-07T1430Z-part-1-of-3.zip",
                            "description": "Nazwa pliku, pod którą zapisywany jest pobierany plik (Content-Disposition): proofsnap-captures-<data i godzina UTC uruchomienia zadania>, z dopiskiem -part-N-of-M, gdy części jest kilka."
                          },
                          "download_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true,
                            "description": "Wstępnie podpisany adres GET pliku ZIP części, ważny do url_expires_at. null, gdy zadanie wygasło."
                          }
                        }
                      }
                    },
                    "download_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Koniec okna pobierania: 7 dni od północy UTC poprzedzającej rozpoczęcie pierwszej części (po tym czasie reguły cyklu życia magazynu usuwają części)."
                    },
                    "url_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Kiedy wartości download_url z tej odpowiedzi przestaną działać: najwyżej po 24 godzinach, nigdy później niż download_expires_at i nigdy później niż wygasną poświadczenia, które je podpisały. Następnie odpytaj ponownie, aby otrzymać nowe linki."
                    },
                    "error": {
                      "type": "string",
                      "nullable": true,
                      "example": "export timed out"
                    },
                    "expired_reason": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "window_ended",
                        "superseded",
                        "rights_changed",
                        "capture_erased",
                        "company_deleted",
                        null
                      ],
                      "description": "Ustawiane, gdy status to expired: window_ended (minął download_expires_at), superseded (nowszy eksport o tym samym zakresie), rights_changed (Twoje uprawnienia nie obejmują już zadania), capture_erased (zawarte w nim przechwycenie zostało wymazane), company_deleted (dane jego firmy zostały usunięte)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "EXPORT_JOB_NOT_FOUND: nie ma takiego zadania, zadanie należy do innego konta, jego rekord wygasł (8 dni po zakończeniu) albo żądanie wysłano testowym kluczem API.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions": {
      "get": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "listWebhookSubscriptions",
        "summary": "Lista subskrypcji webhooków",
        "description": "Subskrypcje webhooków konta, od najnowszych, aktywne i wyłączone, bez sekretów. Klucz API lub sesja w panelu; może go użyć także klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Subskrypcje.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "subscriptions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WebhookSubscription"
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "createWebhookSubscription",
        "summary": "Subskrypcja zdarzeń konta",
        "description": "Zapisz endpoint https do subskrypcji zdarzeń konta: każde pasujące zdarzenie konta jest do niego wysyłane metodą POST, podpisane własnym sekretem subskrypcji (zwracanym jednorazowo w tej odpowiedzi). Wymaga potwierdzonego adresu e-mail (403 EMAIL_NOT_VERIFIED). Dozwolone także przy zaległej płatności za plan (nic nie jest rozliczane). Limity AKTYWNYCH subskrypcji zdarzeń na konto: 25 w developer, startup, growth, custom, legal_solo i legal_seats, 10 w website_archive, 0 w archive_only i sandbox (409 WEBHOOK_SUBSCRIPTION_LIMIT). URL podlega zasadom dla webhook_url (https, bez danych uwierzytelniających, bez adresu prywatnego ani zarezerwowanego; sprawdzane ponownie przy każdym dostarczeniu). Obsługiwany jest Idempotency-Key: ponowienie z tym samym kluczem i tą samą treścią w ciągu 24 godzin otrzymuje z powrotem pierwszą odpowiedź, łącznie z sekretem, zamiast tworzyć drugą subskrypcję. Klucz API lub sesja panelu; może z niego korzystać także klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "events"
                ],
                "additionalProperties": false,
                "properties": {
                  "url": {
                    "type": "string",
                    "example": "https://hooks.example.com/proofsnap"
                  },
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Zwykły tekst, najwyżej 200 znaków."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Utworzono. `secret` jest wyświetlany tylko tutaj.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionCreated"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (events, filters, description, nieznane pole), MISSING_FIELD (url) lub INVALID_URL.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT, IDEMPOTENCY_IN_PROGRESS.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED (URL wskazuje zablokowany adres) lub IDEMPOTENCY_KEY_REUSED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions/{subscription_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/SubscriptionId"
        }
      ],
      "get": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "getWebhookSubscription",
        "summary": "Pobranie subskrypcji webhooka",
        "description": "Jedna subskrypcja webhooka ze stanem dostarczania, bez sekretu. Klucz API lub sesja panelu; może jej użyć także klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Subskrypcja.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: nieznana, usunięta lub należąca do innego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "updateWebhookSubscription",
        "summary": "Zmiana lub reaktywacja subskrypcji webhooka",
        "description": "Zmień events, filters (null lub {} je czyści) albo description lub reaktywuj wyłączoną subskrypcję za pomocą status \"active\" (zeruje liczniki niepowodzeń; sprawdzane względem limitu aktywnych subskrypcji). URL nie można zmienić: utwórz nową subskrypcję. Wymaga potwierdzonego adresu e-mail. Klucz API lub sesja w panelu; może jej używać klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "properties": {
                  "events": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 9,
                    "uniqueItems": true,
                    "items": {
                      "$ref": "#/components/schemas/WebhookEventType"
                    }
                  },
                  "filters": {
                    "type": "object",
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/WebhookSubscriptionFilters"
                      }
                    ],
                    "nullable": true
                  },
                  "description": {
                    "type": "string",
                    "maxLength": 200,
                    "nullable": true
                  },
                  "status": {
                    "type": "string",
                    "enum": [
                      "active"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Zmieniona subskrypcja.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscription"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: nieznana, usunięta lub należąca do innego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT (ponowna aktywacja powyżej limitu).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "deleteWebhookSubscription",
        "summary": "Usunięcie subskrypcji webhooka",
        "description": "Usuwa subskrypcję, jej sekret i oczekujące w kolejce dostarczenia. 404 dla nieznanej lub już usuniętej subskrypcji (traktuj ją jako nieistniejącą). Klucz API lub sesja w panelu; może jej użyć także klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Usunięto.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: nieznana, usunięta lub należąca do innego konta.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "listEvents",
        "summary": "Dziennik zdarzeń",
        "description": "Wszystkie zdarzenia wyemitowane dla konta w ciągu ostatnich 30 dni, niezależnie od tego, czy otrzymała je jakakolwiek subskrypcja, od najnowszego. Klucz API widzi zdarzenia środowiska, w którym działa (produkcyjny klucz API zdarzenia produkcyjne, testowy klucz API zdarzenia testowe); sesja panelu widzi zdarzenia produkcyjne. Użyj tego endpointu, aby uzupełnić pominięte zdarzenia lub odpytywać zamiast subskrybować. Klucz API lub sesja panelu; może go używać także klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Tylko ten typ zdarzenia.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Od 1 do 100, domyślnie 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Identyfikator zdarzenia: strona zaczyna się po nim (przekaż next_cursor). Identyfikator innego konta lub środowiska albo starszy niż 30 dni zwraca 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Strona zdarzeń.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true,
                      "description": "Przekaż jako starting_after, aby pobrać następną stronę; null na ostatniej stronie."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (type, limit lub starting_after).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Webhooki"
        ],
        "operationId": "whoami",
        "summary": "Kim jestem",
        "description": "Opis poświadczenia: plan, środowisko, czy klucz API jest tylko do odczytu, oraz identyfikator, nazwa i prefiks klucza (null dla sesji w panelu). Integracje używają go do przetestowania połączenia i do jego oznaczenia. Bez adresu e-mail i bez identyfikatora konta. Klucz API lub sesja w panelu; może go użyć także klucz tylko do odczytu (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Poświadczenie.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "plan": {
                      "type": "string",
                      "nullable": true,
                      "example": "startup",
                      "enum": [
                        "developer",
                        "startup",
                        "growth",
                        "custom",
                        "legal_solo",
                        "legal_seats",
                        "website_archive",
                        "archive_only",
                        "sandbox",
                        null
                      ]
                    },
                    "api_read_only": {
                      "type": "boolean",
                      "description": "true dla klucza API konta Website Archive (może odczytywać dane i subskrybować zdarzenia, ale nie może tworzyć przechwyceń)."
                    },
                    "env": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ],
                      "description": "Środowisko, w którym działa poświadczenie."
                    },
                    "key": {
                      "type": "object",
                      "nullable": true,
                      "properties": {
                        "key_id": {
                          "type": "string"
                        },
                        "name": {
                          "type": "string",
                          "nullable": true
                        },
                        "prefix": {
                          "type": "string",
                          "enum": [
                            "ps_live_",
                            "ps_test_"
                          ]
                        }
                      }
                    },
                    "email_verified": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/events": {
      "get": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "listCaptureEvents",
        "summary": "Łańcuch zdarzeń przechwyceń (usunięcia i blokady prawne)",
        "description": "Rejestr konta umożliwiający wykrycie nieuprawnionych zmian, który zapisuje, co działo się z jego przechwyceniami: przeniesienie do kosza, przywrócenie, trwałe usunięcie (erase), opróżnienie kosza, usunięcie po upływie okresu przechowywania, usunięcie wraz z kontem lub firmą, nałożenie i zdjęcie blokady prawnej (zdjęcie przez system po zakończeniu subskrypcji ma actor `system` i detail `subscription_ended`), wynik S3 Object Lock oraz import pakietu dowodowego (type `import`, detail = rodzaj pakietu). Od najstarszych. Łańcuch przetrwa przechwycenia i dane konta: zawiera tylko identyfikatory i skróty manifestów, bez treści przechwyceń i bez dowolnego tekstu.\n\nWeryfikacja: (1) dla każdego zdarzenia oblicz SHA-256 z kanonicznego JSON pól objętych skrótem (verification.hashed_fields; klucze posortowane, bez białych znaków, brakujące wartości jako null) i porównaj go z `hash`; (2) sprawdź, czy każdy `prev_hash` jest równy `hash` poprzedniego zdarzenia (64 zera dla seq 1) i czy w seq nie ma luk; (3) dla zakotwiczenia sprawdź, czy `head_hash` jest równy `hash` zdarzenia `to_seq`, zapisz `ots_proof_base64` do pliku (po zdekodowaniu z base64) i uruchom `ots verify -d <head_hash> anchor.ots` (klient OpenTimestamps): potwierdza to, że łańcuch aż do tego zdarzenia istniał najpóźniej w chwili wydobycia bloku Bitcoina. Zakotwiczenia są tworzone raz dziennie dla każdego konta, które ma zdarzenia z wcześniejszego dnia.\n\nKlucz API lub sesja w panelu, własne konto; `scope=company` daje właścicielowi firmy łańcuch przechwyceń firmy (`org` wybiera jedną, gdy wywołujący jest właścicielem kilku; firmę zachowaną jako archiwum firmowe tylko przez sesję w panelu). Testowy klucz API otrzymuje 403 FORBIDDEN: łańcuch obejmuje przechwycenia produkcyjne i nie da się go filtrować bez naruszenia jego ciągłości.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Od 1 do 500, domyślnie 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Wartość seq zdarzenia: strona zaczyna się po nim (przekaż next_starting_after).",
            "schema": {
              "type": "integer",
              "minimum": 0
            }
          },
          {
            "name": "scope",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "own",
                "company"
              ],
              "default": "own"
            }
          },
          {
            "name": "org",
            "in": "query",
            "required": false,
            "description": "scope=company: identyfikator firmy.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Strona łańcucha.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "chain": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string",
                      "enum": [
                        "own",
                        "company"
                      ]
                    },
                    "head": {
                      "type": "object",
                      "properties": {
                        "chain": {
                          "type": "string"
                        },
                        "seq": {
                          "type": "integer"
                        },
                        "head_hash": {
                          "type": "string",
                          "nullable": true
                        },
                        "anchored_seq": {
                          "type": "integer"
                        }
                      }
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEvent"
                      }
                    },
                    "has_more": {
                      "type": "boolean"
                    },
                    "next_starting_after": {
                      "type": "integer",
                      "nullable": true
                    },
                    "anchors": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/CaptureEventAnchor"
                      }
                    },
                    "verification": {
                      "type": "object",
                      "properties": {
                        "hash": {
                          "type": "string"
                        },
                        "hashed_fields": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "genesis_prev_hash": {
                          "type": "string"
                        },
                        "anchor": {
                          "type": "string"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: nieprawidłowe limit, starting_after lub scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: żądanie wysłano z testowym kluczem API albo ze scope=company przez kogoś, kto nie jest właścicielem firmy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/legal-holds": {
      "post": {
        "tags": [
          "Przechwycenia"
        ],
        "operationId": "bulkLegalHold",
        "summary": "Zbiorcze nakładanie lub zdejmowanie blokad prawnych",
        "description": "Nakłada (enabled true) lub zdejmuje (false) blokadę prawną z wielu przechwyceń naraz. selector zawiera dokładnie jedno z: `capture_ids` (od 1 do 500 ID), `matter_id` (przechwycenia sprawy, którą wywołujący może otworzyć), `host` (własne przechwycenia wywołującego z tej nazwy hosta) lub `schedule_id` (własne przechwycenia wywołującego wykonane przez jedno z jego automatycznych przechwyceń). Każde przechwycenie przechodzi przez reguły PATCH /captures/{capture_id} z akcją legal_hold i otrzymuje własny wynik: applied, unchanged (już w tym stanie) albo odmowa z kodem błędu (np. FORBIDDEN, CAPTURE_IN_TRASH, CAPTURE_PROCESSING, LEGAL_HOLD_OWNER_ONLY, LEGAL_HOLD_MATTER, LEGAL_HOLD_SOURCE dla zaczernienia, którego przechwycenie źródłowe jest nadal objęte blokadą, CAPTURE_NOT_FOUND, LEGAL_HOLD_NOT_AVAILABLE dla przechwycenia testowego). Zaczernienia utworzone z każdego przechwycenia przejmują jego blokadę, także w przypadku przechwycenia zgłoszonego jako unchanged; jeśli nie zakończy się to w ramach żądania, zostanie dokończone w ciągu kilku minut. Nakładanie blokad wymaga płatnego planu: bezpłatny tryb testowy (sandbox) otrzymuje dla całego żądania 403 LEGAL_HOLD_NOT_AVAILABLE (details.reason \"plan\"); zdejmowanie działa w każdym planie. Blokada nałożona tutaj ma legal_hold_source \"bulk\"; jej zdjęcie zbiorczo lub pojedynczo działa tak samo. Najwyżej 500 przechwyceń na żądanie: sprawa, host lub automatyczne przechwycenie z większą liczbą jest obcinane (`truncated: true`, wyślij żądanie ponownie). Blokada prawna S3 Object Lock pierwszych 25 zmienionych przechwyceń jest stosowana w ramach żądania, pozostałych w ciągu kilku minut (`s3_legal_hold.status` pending). Koszt: jedna jednostka limitu liczby żądań na każde 10 przechwyceń (co najmniej jedna); żądanie wymagające więcej, niż pozwala jedno okno, zwraca 400 RATE_LIMIT_COST_TOO_HIGH. Klucz API lub sesja panelu; w planie Website Archive klucz API jest tylko do odczytu (403 PLAN_API_READ_ONLY). Sesja panelu lub klucz, których adres e-mail nie został potwierdzony, otrzymują 403 EMAIL_NOT_VERIFIED. Zaległa płatność subskrypcji tego nie blokuje.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "enabled",
                  "selector"
                ],
                "additionalProperties": false,
                "properties": {
                  "enabled": {
                    "type": "boolean"
                  },
                  "reason": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Zwykły tekst zapisywany przy każdej nałożonej blokadzie (nie przy zdjęciu); nigdy nie trafia do łańcuchów objętych skrótami."
                  },
                  "selector": {
                    "type": "object",
                    "minProperties": 1,
                    "maxProperties": 1,
                    "properties": {
                      "capture_ids": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 500,
                        "items": {
                          "type": "string"
                        }
                      },
                      "matter_id": {
                        "type": "string"
                      },
                      "host": {
                        "type": "string"
                      },
                      "schedule_id": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Wyniki dla poszczególnych przechwyceń.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "enabled": {
                      "type": "boolean"
                    },
                    "selector": {
                      "type": "string",
                      "enum": [
                        "capture_ids",
                        "matter_id",
                        "host",
                        "schedule_id"
                      ]
                    },
                    "requested": {
                      "type": "integer"
                    },
                    "truncated": {
                      "type": "boolean"
                    },
                    "applied": {
                      "type": "integer"
                    },
                    "unchanged": {
                      "type": "integer"
                    },
                    "refused": {
                      "type": "integer"
                    },
                    "results": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "capture_id": {
                            "type": "string"
                          },
                          "result": {
                            "type": "string",
                            "enum": [
                              "applied",
                              "unchanged",
                              "refused"
                            ]
                          },
                          "code": {
                            "type": "string",
                            "description": "Kod błędu odmowy."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (treść żądania, selektor, więcej niż 500 capture_ids), RATE_LIMIT_COST_TOO_HIGH.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "EMAIL_NOT_VERIFIED, PLAN_API_READ_ONLY, LEGAL_HOLD_NOT_AVAILABLE (nałożenie bez płatnego planu, details.reason plan).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND (matter_id, którego wywołujący nie może otworzyć), SCHEDULE_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports": {
      "post": {
        "tags": [
          "Import pakietu"
        ],
        "operationId": "createImport",
        "summary": "Rozpoczęcie importu pakietu",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Tworzy import i rozpoczyna jego przesyłanie. Wyślij dokładny rozmiar pliku ZIP w bajtach; odpowiedź zawiera jeden wstępnie podpisany adres URL dla PUT na każdą część o rozmiarze 32 MiB (ostatnia jest mniejsza), ważny przez 1 godzinę (`upload.expires_at`; nigdy dłużej niż podpisujące poświadczenia serwera). Wyślij przez PUT bajty każdej części na jej adres URL, zachowaj nagłówek `ETag` każdej odpowiedzi, a następnie w ciągu 24 godzin wywołaj POST /imports/{import_id}/complete. Z `matter_id` przyjęte przechwycenie jest dodawane do tej sprawy: musisz mieć w tej chwili prawo dodawania do niej przechwyceń (otwarta sprawa, do której masz dostęp; import z miejsca w firmie tylko do sprawy tej firmy). Każde utworzenie wlicza się do dziennego limitu 50.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "size_bytes"
                ],
                "additionalProperties": false,
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Wyświetlana nazwa pliku (zwykły tekst: bez znaków sterujących, niewidocznych i znaków kierunku tekstu)."
                  },
                  "size_bytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 2147483648,
                    "description": "Dokładny rozmiar pliku ZIP w bajtach, maksymalnie 2 GiB."
                  },
                  "matter_id": {
                    "type": "string",
                    "description": "Opcjonalne: dodaje przyjęte przechwycenie do tej sprawy."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Import utworzony; prześlij części.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "uploading"
                      ]
                    },
                    "upload": {
                      "type": "object",
                      "properties": {
                        "upload_id": {
                          "type": "string"
                        },
                        "part_size": {
                          "type": "integer",
                          "example": 33554432
                        },
                        "parts": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "part_number": {
                                "type": "integer"
                              },
                              "url": {
                                "type": "string",
                                "format": "uri",
                                "description": "Wstępnie podpisany adres S3 UploadPart: wyślij przez PUT dokładnie size_bytes bajtów (adres URL podpisuje Content-Length) i odczytaj nagłówek odpowiedzi ETag. Żaden inny nagłówek nie jest potrzebny."
                              },
                              "size_bytes": {
                                "type": "integer",
                                "description": "Dokładna długość tej części w bajtach: part_size dla każdej części poza ostatnią, reszta dla ostatniej (część n zaczyna się od (n - 1) x part_size)."
                              }
                            }
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (treść żądania, filename, size_bytes, matter_id, nieznane pole).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: subskrypcja ma zaległą płatność albo jest nieopłacona.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED (details.reason plan: tylko plany Legal; live_credentials_required: testowy klucz API, sesja bez powiązanych rozliczeń lub sandbox), EMAIL_NOT_VERIFIED, FORBIDDEN (import użytkownika miejsca w firmie do sprawy innej firmy).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_LIMIT (trwają 3 importy; details.limit, details.in_progress), MATTER_SEALED, MATTER_ARCHIVED, COMPANY_READ_ONLY.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "IMPORT_TOO_LARGE: size_bytes powyżej 2 GiB (details.max_bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: limit na minutę albo dzienny limit 50 importów (details.action import_create, details.daily_limit).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Import pakietu"
        ],
        "operationId": "listImports",
        "summary": "Lista Twoich importów",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Twoje importy, od najnowszych. Zakończone importy są przechowywane przez 30 dni (przechwycenie przyjętego importu pozostaje). Przesyłanie, którego 24 godziny minęły, jest pokazywane jako expired.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "next_cursor poprzedniej strony."
          }
        ],
        "responses": {
          "200": {
            "description": "Strona importów.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "imports": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Import"
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (limit, cursor).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "Identyfikator importu (imp_, a po nim od 16 do 24 liter i cyfr). Niepoprawny identyfikator zwraca 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "get": {
        "tags": [
          "Import pakietu"
        ],
        "operationId": "getImport",
        "summary": "Pobranie importu",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Import oraz, po zakończeniu, jego wynik. Odpytuj go po POST /imports/{import_id}/complete; weryfikacja trwa zwykle kilka sekund, a pakiet o rozmiarze 2 GiB do kilku minut. Z ?upload=true przy przesyłaniu, które jeszcze trwa, odpowiedź dodaje upload: nowe wstępnie podpisane adresy URL (parts) dla części, których magazyn jeszcze nie ma, oraz części już odebrane (received: part_number, etag, size_bytes; null, gdy nie udało się ich wylistować, wtedy wszystkie części są podpisywane ponownie), albo completed: true, gdy przesyłanie zostało już zakończone (wyślij complete ponownie). Służy do wznowienia po zerwaniu połączenia, po wygaśnięciu adresu URL części (są ważne najwyżej 1 godzinę, okno przesyłania wynosi 24 godziny) lub po przeładowaniu strony. ?upload=true zużywa jedną jednostkę minutowego limitu liczby żądań.",
        "responses": {
          "200": {
            "description": "Import.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Import"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND (także import innego konta).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED (tylko z ?upload=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "name": "upload",
            "in": "query",
            "required": false,
            "description": "true (lub 1): dodaje nowe adresy URL części i odebrane części trwającego przesyłania.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          }
        ]
      },
      "delete": {
        "tags": [
          "Import pakietu"
        ],
        "operationId": "cancelImport",
        "summary": "Anulowanie przesyłania",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Anuluje import, który nadal jest przesyłany: przesyłanie zostaje przerwane, części usunięte, a import usunięty (GET zwraca potem 404).",
        "responses": {
          "200": {
            "description": "Anulowano.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "deleted": {
                      "type": "boolean",
                      "enum": [
                        true
                      ]
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: import nie jest już w stanie przesyłania (details.status).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/imports/{import_id}/complete": {
      "parameters": [
        {
          "name": "import_id",
          "in": "path",
          "required": true,
          "description": "Identyfikator importu (imp_, a po nim od 16 do 24 liter i cyfr). Niepoprawny identyfikator zwraca 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "post": {
        "tags": [
          "Import pakietu"
        ],
        "operationId": "completeImport",
        "summary": "Zakończenie przesyłania i weryfikacja",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Kończy przesyłanie i uruchamia weryfikację. parts jest opcjonalne: wyślij numer każdej części (od 1 do N, po kolei) z ETag zwróconym przez PUT tej części albo pomiń to pole, a serwer odczyta przesłane części z magazynu (przydatne w przeglądarce, która nie może odczytać nagłówka ETag). Sprawdza, czy zapisany rozmiar jest równy size_bytes. Odpytuj GET /imports/{import_id}. Lista części odrzucona przez magazyn lub część jeszcze nieprzesłana (details.missing_parts) daje 400, a przesyłanie pozostaje otwarte: prześlij brakujące części (GET /imports/{import_id}?upload=true zwraca nowe adresy URL) i wyślij żądanie ponownie. Ponowne wysłanie complete po utracie odpowiedzi jest bezpieczne: complete, który jest jeszcze w toku, zwraca 409, przerwany jest przejmowany po 2 minutach, a przesyłanie już zakończone przechodzi do weryfikacji. Rozmiar różny od size_bytes powoduje odrzucenie przesłanych danych (422 IMPORT_SIZE_MISMATCH; import zostaje odrzucony, utwórz nowy).",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "parts": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "required": [
                        "part_number",
                        "etag"
                      ],
                      "properties": {
                        "part_number": {
                          "type": "integer",
                          "minimum": 1
                        },
                        "etag": {
                          "type": "string",
                          "description": "Nagłówek odpowiedzi ETag z żądania PUT danej części, z cudzysłowami lub bez."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Weryfikacja rozpoczęta.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "import_id": {
                      "type": "string"
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "verifying"
                      ]
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (części w niewłaściwej kolejności lub niezgodne z przesłanymi częściami; albo, bez parts, część, która nie została jeszcze przesłana: details.missing_parts).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "IMPORT_NOT_ALLOWED, EMAIL_NOT_VERIFIED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "IMPORT_NOT_FOUND.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "IMPORT_STATE: import nie jest już przesyłany (details.status, na przykład verifying lub expired).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "IMPORT_SIZE_MISMATCH (details.expected, details.actual).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "Wysyłaj klucz API jako `Authorization: Bearer ps_live_...` (wielkość liter w nazwie schematu nie ma znaczenia). Brak nagłówka lub nagłówek o błędnym formacie zwraca 401 MISSING_API_KEY. Nieznany, unieważniony lub wygasły klucz albo konto bez dostępu do API zwraca 401 INVALID_API_KEY z `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. Kod 403 jest zarezerwowany dla ważnego poświadczenia, któremu nie wolno czegoś zrobić (FORBIDDEN, ograniczenia planu). Klucz `ps_live_` konta bez powiązanego konta rozliczeniowego (klient Stripe) działa jak testowy klucz API: przechwycenia mają znak wodny, nigdy nie są rozliczane, a include_eidas jest odrzucane z 403 EIDAS_REQUIRES_LIVE_KEY. Bramka buforuje decyzję dla klucza do 60 sekund, więc unieważniony klucz może działać jeszcze do 60 sekund. Jeśli poświadczenia nie można sprawdzić z powodu tymczasowej awarii backendu, bramka zwraca zamiast tego 503 SERVICE_UNAVAILABLE; ta odpowiedź nie jest buforowana, więc ponów żądanie z rosnącym odstępem (backoff). Wyzwanie `WWW-Authenticate` jest zwracane bez zmian przy 401 z bramki (sprawdzanie poświadczenia); przy 401 z obsługi endpointu bramka dostarcza to samo wyzwanie pod zmienioną nazwą `x-amzn-Remapped-WWW-Authenticate`."
      }
    },
    "headers": {
      "XRequestId": {
        "description": "Identyfikator żądania, przydatny także w zgłoszeniach do wsparcia. Wysyłany w odpowiedziach zakończonych powodzeniem; treść błędu zawiera go jako request_id.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Liczba żądań dozwolonych w bieżącym 60-sekundowym oknie. Starszy nagłówek, zachowany obok RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Liczba żądań pozostałych w bieżącym oknie. Starszy nagłówek, zachowany obok RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Czas w formacie ISO 8601, w którym kończy się bieżące okno. Starszy nagłówek, zachowany obok RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "RetryAfter": {
        "description": "Liczba sekund oczekiwania przed ponowieniem.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaLimit": {
        "description": "Przechwycenia zawarte w planie na miesiąc (produkcyjne klucze API w planach z miesięcznym limitem).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "Pozostałe przechwycenia w ramach planu w tym miesiącu.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaUsed": {
        "description": "Zakończone przechwycenia w tym miesiącu.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaCap": {
        "description": "Miesięczne maksimum, które dotyczy wywołującego (wartość domyślna planu, własny limit lub limit firmy).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgLimit": {
        "description": "Tylko członkowie firmy: miesięczny limit całej firmy, jeśli właściciel go ustawił.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgUsed": {
        "description": "Tylko członkowie firmy: przechwycenia dopuszczone w tym miesiącu dla całej firmy.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxLimit": {
        "description": "Tryb testowy sandbox (sesja panelu bez planu): dożywotnia pula przechwyceń testowych ze znakiem wodnym (50).",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxRemaining": {
        "description": "Tryb testowy (sandbox): pozostała pula.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxUsed": {
        "description": "Tryb testowy (sandbox): wykorzystana pula.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplay": {
        "description": "\"true\", gdy ta odpowiedź jest powtórzeniem wcześniejszego żądania z tym samym Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "WWWAuthenticate": {
        "description": "Wyzwanie uwierzytelniające zgodne z RFC 6750, wysyłane z każdą odpowiedzią 401. W przypadku 401 zwróconej przez obsługę ścieżki, a nie przez bramę, API Gateway dostarcza je pod zmienioną nazwą `x-amzn-Remapped-WWW-Authenticate`. Nieprawidłowy, unieważniony lub wygasły klucz API albo błędny lub unieważniony token sesji (INVALID_API_KEY): `Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. Brakujący lub nieprawidłowo sformatowany nagłówek Authorization (MISSING_API_KEY): `Bearer realm=\"ProofSnap API\"`.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer realm=\"ProofSnap API\", error=\"invalid_token\""
      },
      "RateLimitPolicy": {
        "description": "Zasada limitu liczby żądań zgodna z projektem IETF draft-ietf-httpapi-ratelimit-headers (w postaci structured field; oparta na projekcie, więc może się zmienić wraz z nim): `\"default\";q=<requests per window>;w=60`.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";q=120;w=60"
      },
      "RateLimit": {
        "description": "Bieżący stan limitu liczby żądań zgodnie z projektem IETF draft-ietf-httpapi-ratelimit-headers (na podstawie projektu): `\"default\";r=<requests left>;t=<seconds until the window resets>`. Wysyłany w odpowiedziach ścieżek objętych limitem liczby żądań (przechwycenia pojedyncze i wsadowe, compare, redact, certify-hash) po zadziałaniu mechanizmu limitu, nie tylko przy 429 (żądanie odrzucone przed mechanizmem limitu, na przykład z powodu uwierzytelniania, go nie ma).",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";r=117;t=42"
      }
    },
    "parameters": {
      "CaptureId": {
        "name": "capture_id",
        "in": "path",
        "required": true,
        "description": "Identyfikator przechwycenia, certyfikatu, porównania, zaczernienia lub zaimportowanego pakietu. Identyfikatory zaczynają się od cap_, cert_, cmp_, red_ lub imp_. Nieprawidłowy identyfikator zwraca 404 CAPTURE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "cap_a1B2c3D4e5F6g7H8"
        }
      },
      "ScheduleId": {
        "name": "schedule_id",
        "in": "path",
        "required": true,
        "description": "Identyfikator automatycznego przechwycenia. Automatyczne przechwycenie innego konta zwraca 404 SCHEDULE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "sch_a1B2c3D4e5F6g7H8"
        }
      },
      "MatterId": {
        "name": "matter_id",
        "in": "path",
        "required": true,
        "description": "ID sprawy. Dla sprawy, której nie jesteś członkiem, zwracany jest błąd 404 MATTER_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "mat_a1B2c3D4e5F6g7H8"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Opcjonalny klucz klienta, dzięki któremu ponowienie żądania jest bezpieczne. Używane jest tylko pierwsze 255 znaków, a klucze obowiązują w obrębie Twojego konta przez 24 godziny. Ponowienie z tym samym kluczem i tą samą treścią zwraca pierwotną treść odpowiedzi z `Idempotent-Replay: true` (202 dla pojedynczego przechwycenia, partii lub porównania, 201 dla zaczernienia; `status` jest aktualizowany do bieżącego statusu przechwycenia, porównania lub zaczernienia oraz każdego przechwycenia partii); nowe przechwycenie nie jest tworzone ani rozliczane. Ten sam klucz z inną treścią zwraca 422 IDEMPOTENCY_KEY_REUSED. Ponowienie w czasie, gdy pierwsze żądanie jest jeszcze przyjmowane, zwraca 409 IDEMPOTENCY_IN_PROGRESS z pierwotnym ID w `error.details.capture_id` (dla partii batch_id).",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "SubscriptionId": {
        "name": "subscription_id",
        "in": "path",
        "required": true,
        "description": "Identyfikator subskrypcji webhooka. Subskrypcja innego konta zwraca 404 WEBHOOK_SUBSCRIPTION_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "whs_a1B2c3D4e5F6g7H8"
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "RATE_LIMITED: zbyt wiele żądań w bieżącym 60-sekundowym oknie. `error.details.retry_after_seconds` powtarza Retry-After. RateLimit zgłasza r=0 i liczbę sekund do zresetowania okna.",
        "headers": {
          "Retry-After": {
            "$ref": "#/components/headers/RetryAfter"
          },
          "RateLimit-Policy": {
            "$ref": "#/components/headers/RateLimitPolicy"
          },
          "RateLimit": {
            "$ref": "#/components/headers/RateLimit"
          },
          "X-RateLimit-Limit": {
            "$ref": "#/components/headers/RateLimitLimit"
          },
          "X-RateLimit-Remaining": {
            "$ref": "#/components/headers/RateLimitRemaining"
          },
          "X-RateLimit-Reset": {
            "$ref": "#/components/headers/RateLimitReset"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "RATE_LIMITED",
                "message": "Too many requests. See Retry-After header.",
                "details": {
                  "retry_after_seconds": 42
                }
              },
              "request_id": "..."
            }
          }
        }
      },
      "InternalError": {
        "description": "INTERNAL_ERROR: nieoczekiwany błąd serwera. Ponów próbę z wykładniczym wydłużaniem odstępów.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AuthUnavailable": {
        "description": "SERVICE_UNAVAILABLE: bramka nie mogła sprawdzić poświadczenia (tymczasowa awaria backendu). Odpowiedź nie jest buforowana: ponów żądanie z rosnącym odstępem (backoff).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "code": "SERVICE_UNAVAILABLE",
                "message": "Authentication is temporarily unavailable. Please retry."
              },
              "request_id": "..."
            }
          }
        }
      },
      "EidasOrAuthUnavailable": {
        "description": "EIDAS_UNAVAILABLE: zażądano include_eidas, ale kwalifikowane znakowanie czasem nie jest skonfigurowane w tym wdrożeniu; ponów próbę bez include_eidas. SERVICE_UNAVAILABLE: brama nie mogła sprawdzić poświadczenia (tymczasowa awaria zaplecza, niezapisywana w pamięci podręcznej); ponów próbę z narastającym odstępem.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401, dwa kody. MISSING_API_KEY: brak nagłówka Authorization lub ma on nieprawidłowy format (`WWW-Authenticate: Bearer realm=\"ProofSnap API\"`). INVALID_API_KEY: dane uwierzytelniające zostały wysłane, ale są nieważne: nieznany, unieważniony lub wygasły klucz API, konto bez dostępu do API albo nieprawidłowy lub unieważniony token sesji (`WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`). Nie ponawiaj żądania z tymi samymi danymi uwierzytelniającymi. 403 oznacza, że dane uwierzytelniające są ważne, ale nie uprawniają do tej operacji (FORBIDDEN, ograniczenia planu).",
        "headers": {
          "WWW-Authenticate": {
            "$ref": "#/components/headers/WWWAuthenticate"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "examples": {
              "missing": {
                "value": {
                  "error": {
                    "code": "MISSING_API_KEY",
                    "message": "Missing or malformed Authorization header. Send Authorization: Bearer <your API key>."
                  },
                  "request_id": "..."
                }
              },
              "invalid": {
                "value": {
                  "error": {
                    "code": "INVALID_API_KEY",
                    "message": "The API key is invalid, revoked or expired, or the account has no API access."
                  },
                  "request_id": "..."
                }
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "Każdy błąd ma taką postać. Odpowiedzi z błędem zawierają nagłówek `X-Request-ID` z tą samą wartością co `request_id`.\n\nRozmiar żądania: treść żądania może mieć najwyżej 6 MB (limit danych wejściowych Lambda; sama API Gateway zatrzymuje się na 10 MB). Większa treść jest odrzucana, zanim dotrze do kodu API, więc taka odpowiedź nie ma tej postaci.\n\n| HTTP | code | Znaczenie |\n| --- | --- | --- |\n| 400 | `MALFORMED_REQUEST` | Treść nie jest prawidłowym JSON (komunikat \"Request body is not valid JSON\") albo żądanie jest nieprawidłowe: gdy `details.reason` podaje, które pole i dlaczego, `message` powtarza ten powód, w przeciwnym razie brzmi \"The request is invalid.\" |\n| 400 | `MISSING_FIELD` | Brakuje wymaganego pola. |\n| 400 | `INVALID_URL` | URL nie jest prawidłowym adresem URL http(s), jest dłuższy niż 2048 znaków albo webhook_url nie jest https. |\n| 400 | `INVALID_HASH` | certify-hash: skrót nie ma 64 znaków szesnastkowych. |\n| 400 | `INVALID_TIMESTAMP_TYPE` | options.timestamp_type nie ma wartości \"bitcoin\". |\n| 400 | `INVALID_VIEWPORT` | options.viewport nie ma wartości \"desktop\" ani \"mobile\". |\n| 400 | `METADATA_TOO_LARGE` | options.metadata ma więcej niż 10 kluczy, wartość niebędącą ciągiem znaków lub wartość dłuższą niż 500 znaków. |\n| 400 | `INVALID_EVIDENCE_PACK_OPTIONS` | options.evidence_pack jest nieprawidłowe. |\n| 400 | `INVALID_BATCH` | urls partii nie jest tablicą od 2 do 50 prawidłowych adresów URL. |\n| 400 | `REDACTION_NO_OPS` | Zaczernienie nie zawiera żadnych operacji albo żadna z nich niczego nie dopasowała. |\n| 400 | `INVALID_CRON` | Nie udało się przeanalizować cron_expr. |\n| 400 | `INTERVAL_TOO_SHORT` | Częstotliwość harmonogramu jest niższa niż minimum planu. |\n| 400 | `RATE_LIMIT_COST_TOO_HIGH` | Żądanie wymaga więcej jednostek limitu liczby żądań, niż pozwala jedno okno; `details.max_per_window`, `details.window_seconds`, `details.requested`. Podziel je. |\n| 400 | `WEBHOOK_REQUIRES_API_KEY` | `webhook_url` wysłano z sesji w panelu. Webhooki są podpisywane sekretem webhooka klucza API, więc wymagają klucza API. |\n| 401 | `MISSING_API_KEY` | Brak nagłówka Authorization lub jest on nieprawidłowo sformatowany. `WWW-Authenticate: Bearer realm=\"ProofSnap API\"`. |\n| 401 | `INVALID_API_KEY` | Klucz nieznany, unieważniony lub wygasły, brak dostępu do API albo token sesji nieprawidłowy lub unieważniony. `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. |\n| 402 | `QUOTA_EXCEEDED` | Osiągnięto miesięczne maksimum lub przydział trybu testowego (sandbox) (także w `error` certyfikacji w tle odrzuconej z tego powodu). |\n| 402 | `PAYMENT_REQUIRED` | Produkcyjna subskrypcja jest zaległa lub nieopłacona (także w `error` automatycznego przechwycenia pominiętego z tego powodu). |\n| 403 | `FORBIDDEN` | Zasób należy do kogoś innego albo akcja jest dostępna tylko dla właściciela. Komunikat: \"You do not have access to this resource.\" (details.reason podaje więcej, gdy to pomaga). |\n| 403 | `EMAIL_NOT_VERIFIED` | Sesja w panelu, której adres e-mail nie został potwierdzony, próbowała utworzyć coś trwałego, płatnego lub zapraszającego inne osoby (przechwycenia, partie, porównania, zaczernienia, klucze API, sekrety webhooków, płatność, zmiany planu, zmiany miejsc, certify-hash, automatyczne przechwycenia, zaproszenia do firmy, sprawy i wszelkie ich zmiany, członkowie spraw, przechwycenia przypisane do spraw, udostępnienia i ich unieważnienie, pieczęcie, eksporty, blokady prawne). Odczyty pozostają dostępne. Komunikat: \"Confirm your email address first. Use the link we sent you, or send a new one from the dashboard.\" Klucz API jest odrzucany w ten sam sposób, gdy adres e-mail jego konta nie został potwierdzony. |\n| 403 | `EIDAS_REQUIRES_LIVE_KEY` | include_eidas wysłano z testowym kluczem API (klucz ps_live_ konta bez powiązanego konta rozliczeniowego jest tak traktowany) albo dla porównania lub zaczernienia przechwycenia testowego (`details.reason`). Produkcyjny klucz API lub sesja działająca w trybie testowym, ponieważ nie powiązano konta rozliczeniowego, otrzymuje `details.reason` \"no_billing_account\". |\n| 403 | `MATTERS_NOT_ENABLED` | Sprawy wymagają planu Legal. |\n| 403 | `SCHEDULE_LIMIT_REACHED` | Osiągnięto limit aktywnych automatycznych przechwyceń planu (lub plan nie obejmuje automatycznych przechwyceń). |\n| 403 | `ON_CHANGE_REQUIRES_PLAN` | Plan nie pozwala na monitorowanie on_change. |\n| 403 | `PLAN_ARCHIVE_ONLY` | Plan Archive only utrzymuje Twoje dowody w stanie umożliwiającym odczyt, pobranie i weryfikację; nie tworzy przechwyceń, automatycznych przechwyceń, certyfikacji, porównań ani zaczernień. |\n| 403 | `PLAN_NOT_AVAILABLE` | To konto nie może wybrać tego planu: Archive only kupuje się po zakończeniu płatnej subskrypcji API (dopóki dowody są jeszcze przechowywane) lub uzyskuje przez zmianę planu z developer, startup, growth, website_archive lub legal_solo; firmowe archive only tylko właściciel firmy, której subskrypcja się zakończyła, w jej oknie pobierania. `details.reason` podaje przyczynę. |\n| 403 | `PLAN_API_READ_ONLY` | Plan Website Archive: klucz API jest tylko do odczytu (lista, pobieranie, weryfikacja, webhooki). Twórz przechwycenia, automatyczne przechwycenia i certyfikacje w panelu albo użyj planu API. |\n| 403 | `LEGAL_HOLD_OWNER_ONLY` | Tylko właściciel firmy zdejmuje blokadę prawną z przechwycenia firmy (miejsce może ją nałożyć). |\n| 403 | `LEGAL_HOLD_NOT_AVAILABLE` | Nałożenie blokady prawnej wymaga płatnego planu (`details.reason` \"plan\": bezpłatny sandbox lub konto, którego subskrypcja się zakończyła) i przechwycenia produkcyjnego (`details.reason` \"test_capture\": przechwycenie testowe ze znakiem wodnym). Zdjęcie blokady nigdy nie jest z tego powodu odrzucane. |\n| 403 | `IMPORT_NOT_ALLOWED` | Import pakietu wymaga planu Legal (`details.reason` \"plan\") oraz produkcyjnego klucza API lub sesji w panelu z powiązanym rozliczeniem (`details.reason` \"live_credentials_required\"). |\n| 404 | `CAPTURE_NOT_FOUND` | Przechwycenie nie istnieje, zostało usunięte albo identyfikator jest nieprawidłowy. |\n| 404 | `MATTER_NOT_FOUND` | Sprawa nie istnieje lub nie jesteś jej członkiem. |\n| 404 | `IMPORT_NOT_FOUND` | Import nie istnieje, należy do innego konta albo identyfikator jest nieprawidłowy. |\n| 404 | `WEBHOOK_SUBSCRIPTION_NOT_FOUND` | Subskrypcja webhooka nie istnieje, została usunięta albo należy do innego konta. |\n| 404 | `SCHEDULE_NOT_FOUND` | Automatyczne przechwycenie nie istnieje lub należy do innego konta. |\n| 404 | `NOT_FOUND` | Nie znaleziono innego zasobu (format skrótu rejestru, token udostępnienia, zadanie eksportu, członek). |\n| 404 | `EXPORT_JOB_NOT_FOUND` | Zadanie eksportu przechwyceń nie istnieje, należy do innego konta, wygasło albo zażądano go testowym kluczem API. |\n| 405 | `METHOD_NOT_ALLOWED` | Metoda HTTP nie jest dozwolona dla tej ścieżki. |\n| 409 | `CAPTURE_PROCESSING` | Przechwycenie jest wciąż przetwarzane i nie można go jeszcze usunąć (ani zmienić jego blokady prawnej). |\n| 409 | `CAPTURE_LEGAL_HOLD` | Przechwycenie jest objęte blokadą prawną i nie można go usunąć. |\n| 409 | `CAPTURE_IN_TRASH` | Blokada prawna: przechwycenie jest w koszu; najpierw je przywróć. |\n| 409 | `CAPTURE_HOLD_GRACE` | Trwałe usunięcie w okresie ochronnym po zdjęciu blokady prawnej przechwycenia; `details.until`. Przeniesienie do kosza jest dozwolone. |\n| 409 | `LEGAL_HOLD_MATTER` | Blokadę przechwycenia nałożyła blokada prawna jego sprawy, która nadal obowiązuje: zdejmij ją w sprawie (`details.matter_id` dla osoby z dostępem do odczytu sprawy). |\n| 409 | `LEGAL_HOLD_SOURCE` | Blokada zaczernienia lub porównania wynika z przechwycenia, z którego je utworzono (w przypadku porównania z dowolnego z porównywanych przechwyceń), a to jest nadal objęte blokadą prawną (nałożoną na to przechwycenie lub przez jego sprawę): pakiet pozostaje zablokowany do czasu zdjęcia tej blokady (`details.source_capture_id`). |\n| 409 | `COMPANY_READ_ONLY` | Subskrypcja firmy się zakończyła (lub jest ona utrzymywana jako archiwum firmy): jej sprawy można otwierać, eksportować i unieważniać ich linki do udostępniania, nic więcej, a z jej przechwyceń nie powstaje nic nowego. |\n| 409 | `WEBHOOK_SUBSCRIPTION_LIMIT` | Osiągnięto limit aktywnych subskrypcji webhooków planu (25 w planach API i Legal, 10 w Website Archive, 0 w Archive only i sandbox); `details.limit`, `details.active`. |\n| 409 | `WEBHOOK_SECRET_ROTATION_PENDING` | Poprzedni sekret webhooka jest nadal w 24-godzinnym okresie nakładania się; `details.previous_secret_expires_at`. |\n| 409 | `IDEMPOTENCY_IN_PROGRESS` | Pierwsze żądanie z tym Idempotency-Key jest jeszcze przyjmowane. |\n| 409 | `COMPARE_NOT_READY` | Przed porównaniem oba przechwycenia muszą być zakończone. |\n| 409 | `REDACTION_NOT_READY` | Przed zaczernieniem przechwycenie źródłowe musi być zakończone. |\n| 409 | `MATTER_SEALED` | Sprawa (lub sprawa przechwycenia) jest zapieczętowana i nie może się zmienić. |\n| 409 | `MATTER_SEALING` | Trwa pieczętowanie sprawy (najwyżej 60 s): w tym czasie nie można przypisać przechwycenia, zmienić nazwy, zmienić członków, przenieść powiązanego przechwycenia do kosza ani zapieczętować po raz drugi. `Retry-After`. |\n| 409 | `MATTER_ARCHIVED` | Sprawa jest zarchiwizowana: bez nowych przechwyceń, zmian nazwy, nowych członków, udostępniania i eksportu; najpierw otwórz ją ponownie (PATCH status \"open\"). Usunięcie przechwycenia lub członka nadal działa. |\n| 409 | `MATTER_CHANGED` | Dziennik łańcucha dowodowego zmienił się podczas pieczętowania; nic nie zostało zapieczętowane, ponów próbę. |\n| 409 | `MATTER_LINKED` | Usuwanie przechwycenia: przechwycenie jest dowodem w sprawie, do której wywołujący już nie należy. Właściciel sprawy musi je najpierw usunąć ze sprawy. |\n| 409 | `MATTER_HAS_TRASHED_CAPTURES` | Pieczętowanie sprawy: przechwycenie tej sprawy jest w koszu. Przywróć je lub usuń ze sprawy, a następnie zapieczętuj ponownie. |\n| 409 | `MEMBER_HAS_INDIVIDUAL_SUBSCRIPTION` | Zaproszenie do firmy: własna subskrypcja API zaproszonej osoby nadal się odnawia. Osoba ta ją anuluje, a następnie właściciel zaprasza ją ponownie. |\n| 409 | `IMPORT_LIMIT` | W toku są już 3 importy (przesyłanie lub weryfikacja); `details.limit`, `details.in_progress`. |\n| 409 | `IMPORT_STATE` | Import nie jest w stanie, który na to pozwala (complete lub cancel importu, który nie jest już w fazie przesyłania); `details.status`. |\n| 409 | `EXPORT_JOB_RUNNING` | Eksport Twoich przechwyceń jest wciąż przygotowywany; wskazują go `details.job_id` i `details.poll_path`. Rozpocznij nowy, gdy się zakończy. |\n| 413 | `IMPORT_TOO_LARGE` | Pakiet jest większy niż 2 GiB; `details.max_bytes`. |\n| 413 | `VERIFY_PACKAGE_TOO_LARGE` | POST /captures/{capture_id}/verify: zapisany pakiet przekracza 256 MiB; zweryfikuj go offline za pomocą dołączonych skryptów. `details.size_bytes`, `details.limit_bytes`. |\n| 422 | `IDEMPOTENCY_KEY_REUSED` | Idempotency-Key użyty ponownie z inną treścią żądania lub w innym środowisku klucza (live lub test). |\n| 422 | `SSRF_BLOCKED` | URL wskazuje adres prywatny, pętli zwrotnej, link-local lub inny zablokowany. |\n| 422 | `TARGET_UNREACHABLE` | Nazwa hosta z URL nie ma rekordów DNS lub nie udało się jej rozwiązać. |\n| 422 | `REDACTION_SCREENSHOT_UNREADABLE` | Nie udało się zdekodować zrzutu ekranu na potrzeby zaczerniania prostokątami. |\n| 422 | `REDACTION_LEAK_DETECTED` | Zaczerniony termin przetrwał zaczernianie poza URL przechwycenia; nic nie zostało wydane. `details.files`, `details.rule_indexes`, `details.metadata_fields`, `details.in_capture_url`. |\n| 422 | `REDACTION_SOURCE_UNSUPPORTED` | Zaczerniać można tylko przechwycenia stron internetowych. `details.source` lub `details.manifest_type`. |\n| 422 | `REDACTION_SOURCE_INTEGRITY_FAILED` | Plik przechwycenia źródłowego nie zgadza się ze skrótem SHA-256 wymienionym w jego podpisanym manifeście lub go brakuje; nic nie zostało wydane. `details.mismatched`, `details.missing`. |\n| 422 | `COMPARE_PROFILE_MISMATCH` | POST /compare: dwa przechwycenia wykonano z innym profilem urządzenia lub z innymi ustawieniami przechwycenia; `details.differing`, `details.before`, `details.after`. Wyślij allow_profile_mismatch: true, aby mimo to porównać (wynik dla tekstu i obrazów jest wtedy nierozstrzygnięty). |\n| 422 | `COMPARE_URL_MISMATCH` | POST /compare: dwa przechwycenia dotyczą różnych stron; `details.before`, `details.after` (url i final_url jako origin i ścieżka). Wyślij allow_url_mismatch: true, aby mimo to porównać. |\n| 422 | `RULE902_NOT_AVAILABLE_FOR_MISMATCH` | POST /compare: include_rule_902 dla przechwyceń wyrenderowanych w różny sposób (allow_profile_mismatch); `details.differing`. |\n| 422 | `COMPARE_SOURCE_INTEGRITY_FAILED` | Plik przechwycenia źródłowego w POST /compare nie zgadza się ze skrótem SHA-256 wymienionym w jego podpisanym manifeście lub go brakuje; nic nie zostało porównane ani naliczone. `details.capture_id`, `details.mismatched`, `details.missing`, `details.sources[]`. |\n| 422 | `REDACTION_PATTERN_TOO_SLOW` | Wzorzec wyrażenia regularnego z text_redactions działał na tej stronie zbyt długo (katastrofalne nawracanie); nic nie zostało wydane ani naliczone. `details.rule_index`. Uprość wzorzec. |\n| 422 | `IMPORT_SIZE_MISMATCH` | Przesłany pakiet nie ma zapowiedzianego rozmiaru size_bytes; przesłane dane zostały odrzucone. `details.expected`, `details.actual`. |\n| 422 | `IMPORT_NOT_SUPPORTED` | Porównanie lub zaczernienie zaimportowanego pakietu dowodowego. |\n| 429 | `RATE_LIMITED` | Zbyt wiele żądań w bieżącym oknie albo osiągnięto dzienny limit tworzenia spraw (200), pieczętowania (20), eksportu (50), zaproszeń członków (50) lub importów pakietów (50, `import_create`) (`details.daily_limit`, `details.action`). `Retry-After` podaje, kiedy ponowić próbę. |\n| 429 | `CONCURRENT_CAPTURE_LIMIT` | Zbyt wiele przechwyceń konta działa jednocześnie (developer 10, startup 25, growth 100, legal_solo i legal_seats 25, custom 200, website_archive 25, sandbox 5). Partia uruchamia tyle adresów URL, ile się mieści, a resztę wymienia w `not_admitted`; gdy nie ma wolnego miejsca, całe żądanie jest odrzucane. `details.limit`, `details.in_flight`, `details.requested`, `Retry-After`. |\n| 429 | `TEST_MODE_DAILY_LIMIT` | Praca w trybie testowym (testowy klucz API lub sesja bez powiązanego rozliczenia; ze znakiem wodnym, nierozliczana) jest ograniczona do 200 jednostek na konto i dobę UTC (partia liczy swoje adresy URL). `details.used`, `details.limit`, `details.reset_at`, `Retry-After`. |\n| 429 | `IMPORT_DAILY_BYTES_LIMIT` | Import pakietów: osiągnięto wolumen importu konta na dobę UTC (5 GiB zapowiedzianych rozmiarów). `details.period`, `details.limit_bytes`, `details.used_bytes`, `details.requested_bytes`, `details.reset_at`. |\n| 429 | `IMPORT_MONTHLY_BYTES_LIMIT` | Import pakietów: osiągnięto wolumen importu konta na miesiąc UTC (20 GiB). Te same szczegóły. |\n| 500 | `INTERNAL_ERROR` | Nieoczekiwany błąd. Ponów próbę z narastającym odstępem. |\n| 500 | `CAPTURE_FAILED` | Nie udało się rozpocząć przechwycenia albo (w `error` nieudanego przechwycenia) nie udało się go zakończyć. |\n| 500 | `CAPTURE_TIME_BUDGET_EXCEEDED` | W `error` nieudanego przechwycenia lub przy synchronicznym zaczernianiu: uruchomienie wyczerpało swój budżet czasu i nie wydano ani nie naliczono żadnego pakietu. |\n| 500 | `CAPTURE_SCREENSHOT_TIMEOUT` | W `error` nieudanego przechwycenia: przeglądarka nie zakończyła renderowania zrzutu ekranu: każdy sposób jego wykonania (jednym ujęciem, mniejszymi pasami, własnym zrzutem przeglądarki) przekroczył limit czasu, podobnie jak jedno ponowienie w nowo uruchomionej przeglądarce, gdy budżet czasu na nie pozwalał. Nic nie zostało naliczone; spróbuj ponownie. |\n| 500 | `CERTIFY_FAILED` | W `error` nieudanej certyfikacji: nie udało się zakończyć certyfikacji. |\n| 500 | `REDACTION_FAILED` | W `error` nieudanego zaczernienia: nie udało się zakończyć zaczernienia. Zaczernienie odrzucone ze znanego powodu zawiera zamiast tego kod tego powodu (na przykład REDACTION_LEAK_DETECTED). |\n| 500 | `TIMESTAMP_SERVICE_UNAVAILABLE` | Pieczętowanie sprawy: zakotwiczenie OpenTimestamps nie powiodło się; nic się nie zmieniło, ponów próbę. |\n| 500 | `CONFIGURATION_ERROR` | Krok po stronie serwera nie powiódł się lub nie jest skonfigurowany (`details.reason`). |\n| 503 | `SERVICE_UNAVAILABLE` | Uwierzytelnianie jest tymczasowo niedostępne (nie udało się sprawdzić poświadczenia). Niezapisywane w pamięci podręcznej; ponów próbę. |\n| 503 | `EIDAS_UNAVAILABLE` | Zażądano include_eidas, ale znakowanie czasem eIDAS nie jest skonfigurowane. |\n| 503 | `EIDAS_SEAL_RETRY` | Pieczętowanie sprawy: znacznik czasu eIDAS nie nadszedł na czas; nic nie zostało zapieczętowane. Ponów próbę po `Retry-After`; ponowienie wykorzystuje znacznik czasu już wydany. |\n| 503 | `LIMIT_CHECK_UNAVAILABLE` | Pieczętowanie sprawy: nie udało się sprawdzić jego dziennego limitu; nic nie zostało wykonane. Ponów próbę. |\n| 504 | `UPSTREAM_TIMEOUT` | Synchroniczny krok przetwarzania (certify-hash, eksport sprawy) nie odpowiedział na czas. Może się jeszcze zakończyć: odpytuj to, co wskazuje `details` (`certify_id`, `poll_path`), lub ponów próbę. |\n| 404 | `ORG_INVITE_NOT_FOUND` | Zaproszenie do firmy, które jest nieznane, skierowane do kogoś innego, już nieoczekujące lub którego firma już nie działa (details.reason `company_inactive`). |\n| 409 | `ORG_INVITE_NO_SEATS` | Przyjmowanie zaproszenia do firmy: wszystkie miejsca firmy są zajęte; zaproszenie pozostaje otwarte. |\n| 409 | `ORG_INVITE_IN_OTHER_ORG` | Przyjmowanie zaproszenia do firmy: wywołujący zajmuje miejsce w innej firmie; zaproszenie pozostaje otwarte. |\n| 409 | `ORG_INVITE_HAS_SUBSCRIPTION` | Przyjmowanie zaproszenia do firmy: własna subskrypcja API wywołującego nadal się odnawia; zaproszenie pozostaje otwarte. |\n| 409 | `ORG_OWNER_CANNOT_LEAVE` | Właściciel firmy próbował opuścić własną firmę. |\n| 410 | `ORG_INVITE_EXPIRED` | Przyjmowanie zaproszenia do firmy starszego niż 7 dni. |",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Kod błędu czytelny maszynowo (UPPER_SNAKE_CASE)."
              },
              "message": {
                "type": "string",
                "description": "Komunikat czytelny dla człowieka."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Opcjonalny dodatkowy kontekst, np. `reason`, `field` lub `retry_after_seconds`."
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "Identyfikator żądania."
          }
        },
        "example": {
          "error": {
            "code": "INVALID_URL",
            "message": "URL is not a valid http(s) URL",
            "details": {
              "reason": "Only http and https schemes are allowed"
            }
          },
          "request_id": "..."
        }
      },
      "CaptureOptions": {
        "type": "object",
        "description": "Opcje przechwycenia. Każde pole jest opcjonalne.",
        "properties": {
          "scroll_full_page": {
            "type": "boolean",
            "description": "Przechwyć całą przewijaną stronę. false przechwytuje tylko widoczny obszar (viewport).",
            "default": true
          },
          "expand_collapsed": {
            "type": "boolean",
            "description": "Rozwija zwinięte sekcje przed wykonaniem zrzutu ekranu: zamknięte elementy <details>, przyciski akordeonów i sekcji rozwijanych (aria-expanded z aria-controls) oraz tekstowe kontrolki \"see more\" / \"show replies\". Z zabezpieczeniami: nic wewnątrz nawigacji, nagłówków, menu ani okien dialogowych, żadnych linków do innego adresu URL, żadnego wysyłania formularzy, żadnych etykiet zmieniających stan (like, follow, buy, delete, ...), każda kontrolka najwyżej raz, a kontrolka, która próbuje opuścić stronę, przerywa rozwijanie i ponownie wczytywany jest pierwotny adres URL. Wynik jest zapisywany dla każdej kontrolki w forensic_log.json (page_modifications.expand_collapsed: znalezione, otwarte, pozostawione zamknięte, powody pominięcia, uruchomione żądania, wysokość strony przed i po) z podsumowaniem w metadata.json (expand_collapsed: requested, opened_total, left_closed_total); evidence.pdf i certyfikaty podają tylko to, co faktycznie zostało otwarte.",
            "default": true
          },
          "viewport": {
            "type": "string",
            "description": "desktop to 1920x1080 przy współczynniku pikseli 1; mobile to 390x844 przy współczynniku pikseli 3 z user agentem mobilnego Safari. Inne wartości zwracają 400 INVALID_VIEWPORT.",
            "enum": [
              "desktop",
              "mobile"
            ],
            "default": "desktop"
          },
          "viewport_width": {
            "type": "number",
            "minimum": 320,
            "maximum": 3840,
            "description": "Niestandardowa szerokość okna widoku w pikselach CSS. Musi być wysłana razem z viewport_height, w przeciwnym razie 400 MALFORMED_REQUEST."
          },
          "viewport_height": {
            "type": "number",
            "minimum": 240,
            "maximum": 4320,
            "description": "Niestandardowa wysokość okna widoku w pikselach CSS. Musi być wysłana razem z viewport_width."
          },
          "device_scale_factor": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "description": "Współczynnik pikseli urządzenia zrzutu ekranu."
          },
          "wait_for_selector": {
            "type": "string",
            "description": "Przed przechwyceniem czeka do 10 sekund na ten selektor CSS. Jeśli się nie pojawi, przechwytywanie jest kontynuowane bez błędu. Zapisywane w page_modifications w forensic_log.json.",
            "maxLength": 200
          },
          "wait_ms": {
            "type": "number",
            "minimum": 0,
            "maximum": 15000,
            "description": "Dodatkowy czas oczekiwania w milisekundach po załadowaniu strony."
          },
          "screenshot_format": {
            "type": "string",
            "description": "jpeg dostarcza screenshot.jpeg; png dostarcza bezstratny screenshot.png. C2PA Content Credentials są dodawane tylko do zrzutów ekranu w formacie JPEG.",
            "enum": [
              "jpeg",
              "png"
            ],
            "default": "jpeg"
          },
          "url_stamp": {
            "type": "boolean",
            "description": "Dodaj nad obrazem strony pasek nagłówka z przechwyconym URL, czasem przechwycenia w UTC (chwila, w której przeglądarka wykonała zrzut ekranu; w przypadku długiej strony zrzucanej pasami chwila wykonania ostatniego pasa) i identyfikatorem dowodu, oznaczony jako adnotacja programowa. Pasek jest doklejany nad obrazem, więc nie zasłania żadnego piksela strony. chain_of_custody.json zapisuje screenshot_url_header.raw_screenshot_sha256 wraz z raw_screenshot_source: dla strony zrzuconej w jednym kawałku (browser_image, browser_png, browser_jpeg_joined, browser_jpeg_reencoded) jest to SHA-256 obrazu strony wytworzonego przez przeglądarkę, przed dodaniem paska; dla długiej strony zrzuconej pasami i połączonej (joined_bands, joined_bands_lossless, joined_bands_reencoded) jest to SHA-256 połączonego obrazu w postaci zapisanej przez ProofSnap z paskiem, przed dodaniem C2PA Content Credentials. Gdy paska nie da się dodać (obraz z paskiem przekroczyłby 65 535 px, które mieści JPEG, rysowanie się nie powiodło albo, tylko w zapasowej ścieżce zrzutu ekranu, obraz strony ma ponad 32 000 000 pikseli), zrzut ekranu jest dostarczany bez niego: metadata.json zapisuje to w screenshot.url_stamp_skipped, a chain_of_custody.json w screenshot_url_header (applied false, z przyczyną).",
            "default": false
          },
          "network_log": {
            "type": "boolean",
            "description": "Dołącza network.har (HAR z nagłówkami żądań i odpowiedzi oraz czasami, bez treści) i network.original.har.",
            "default": true
          },
          "warc": {
            "type": "boolean",
            "description": "Dołącza capture.warc (ISO 28500) z transakcjami HTTP z ładowania strony. Treści odpowiedzi są odczytywane w limicie czasu (maksymalnie 30 s i tylko wtedy, gdy pozwala na to opcjonalna część budżetu czasu przechwycenia, po odliczeniu czasu zarezerwowanego na eIDAS, C2PA i końcowy odczyt zegara); treść przerwana przez ten limit jest zapisywana jako pusta z WARC-Truncated: time, odpowiedź, do której nie doszło, jest pomijana, a warc_captured w chain_of_custody.json (oraz warc w forensic_log.json) zapisuje truncated, truncation_reason, response_pairs, records_total i liczniki.",
            "default": false
          },
          "external_archive": {
            "type": "boolean",
            "description": "Poproś usługę Save Page Now z Internet Archive o zarchiwizowanie URL (Internet Archive przechowuje tę kopię publicznie). Wynik jest klasyfikowany według tego, czyje żądanie wytworzyło migawkę, i jego czasu względem żądania: created (utworzony dla tego żądania), recent_existing (Internet Archive odpowiedziało wcześniejszą migawką), created_after_request (istnieje nowsza migawka, ale żądanie nie zostało potwierdzone), existing (istnieje tylko starsza migawka), pending, rate_limited, failed (Internet Archive odrzuciło żądanie), unconfirmed (żadna odpowiedź go nie potwierdziła ani nie odrzuciła i nie jest znany żadna migawka), unavailable, skipped, skipped_sensitive lub skipped_query. URL, które wyglądają na zawierające dane uwierzytelniające albo mają parametry, nazwy parametrów lub segmenty ścieżki przypominające tokeny, nigdy nie są wysyłane. URL z ciągiem zapytania jest wysyłany tylko wtedy, gdy ta opcja zostanie jawnie ustawiona na true; przy wartości domyślnej jest pomijany (skipped_query). Wynik jest zapisywany w forensic_log.json (external_archive, z snapshot_offset_seconds względem przechwycenia) i chain_of_custody.json oraz zwracany jako external_archive przez GET /captures/{capture_id}. Dla prywatnych URL ustaw false. Żądanie jest wysyłane, gdy tylko strona się załaduje, równolegle z resztą przechwycenia. Niepowodzenie nie powoduje niepowodzenia przechwycenia.",
            "default": true
          },
          "dismiss_consent": {
            "type": "boolean",
            "description": "Przed wczytaniem strony ustawia pliki cookie zgody typu „zaakceptuj wszystkie” i próbuje zamknąć banery zgody na pliki cookie. Zmienia to stronę, dlatego opcja jest domyślnie wyłączona; to, co zostało wykonane, jest zapisywane w forensic_log.json consent_dismissal.",
            "default": false
          },
          "dismiss_popups": {
            "type": "boolean",
            "description": "Zamyka wyskakujące okna z newsletterem, promocjami i podobne nakładki za pomocą ich własnego przycisku zamykania. Liczba zamkniętych okien i użyte przyciski są zapisywane w forensic_log.json page_modifications. Każde zamknięte okno jest zapisywane w forensic_log.json page_modifications (popups_dismissed, popups_dismissed_controls); evidence.pdf podaje, ile okien zamknięto.",
            "default": false
          },
          "dark_mode": {
            "type": "boolean",
            "description": "Renderuj w ciemnym schemacie kolorów. Zmienia wygląd strony; zapisywane w page_modifications w forensic_log.json. Ujawniane w evidence.pdf i w page_modifications w forensic_log.json.",
            "default": false
          },
          "block_ads": {
            "type": "boolean",
            "description": "Blokuj żądania do znanych hostów reklamowych i śledzących. Pomija część treści; zapisywane w forensic_log.json page_modifications.",
            "default": false
          },
          "block_resources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "image",
                "font",
                "media",
                "stylesheet"
              ]
            },
            "description": "Typy zasobów do zablokowania. Pomija treść; odnotowywane w page_modifications w forensic_log.json."
          },
          "inject_css": {
            "type": "string",
            "description": "CSS dodawany do strony przed przechwyceniem. Zmienia stronę; zapisywany dosłownie w forensic_log.json page_modifications. Zawsze zapisywany dosłownie w forensic_log.json page_modifications (injected_css) z injected_css_status `applied` lub `failed` oraz injected_css_error. evidence.pdf informuje, że wstrzyknięto własny CSS; nie drukuje kodu.",
            "maxLength": 10000
          },
          "inject_js": {
            "type": "string",
            "description": "Kod JavaScript uruchamiany na stronie przed przechwyceniem. Zmienia stronę. Zawsze zapisywany dosłownie w forensic_log.json page_modifications (injected_js) wraz z injected_js_status i injected_js_error: `applied` (wykonał się do końca), `invalid` (nie skompilował się, na przykład z powodu błędu składni, więc nigdy się nie uruchomił i nie zmienił strony), `failed` (zgłosił wyjątek podczas działania, więc mógł zmienić stronę, zanim się zatrzymał) lub `timed_out` (przechwytywanie przestało na niego czekać po 5 sekundach; skrypt mógł nadal działać i zmieniać stronę). evidence.pdf podaje wynik i odsyła do forensic_log.json, a certyfikat pochodzenia wymienia wstrzyknięty kod wśród modyfikacji strony, chyba że miał status `invalid`; żaden z tych dokumentów nie wypisuje kodu.",
            "maxLength": 10000
          },
          "locale": {
            "type": "string",
            "description": "Emulowane ustawienia regionalne przeglądarki, na przykład de lub de-DE (ustawiają język przeglądarki i Accept-Language). Tylko emulacja: nie jest używany serwer proxy, więc treść zależna od adresu IP odwiedzającego nadal odpowiada regionowi serwera przechwytującego. Zapisywane w metadata.json geo.",
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "timezone": {
            "type": "string",
            "description": "Emulowana strefa czasowa przeglądarki (nazwa IANA, na przykład Europe/Berlin). Tylko emulacja."
          },
          "language": {
            "type": "string",
            "description": "Język plików PDF czytelnych dla człowieka. Jedna z wartości: en, de, it, fr, es, pt_BR, cs, el, hi, hu, id, ja, ko, nl, pl, ro, ru, sv, tr, vi, zh_CN, zh_TW, ar, he, da, fi, nb, sk, uk. Wielkość liter, łącznik i region są tolerowane (\"DE\", \"fr-FR\" i \"PT-BR\" są akceptowane); samo \"pt\" i \"zh\" jest odrzucane. Nieobsługiwana wartość zwraca 400 MALFORMED_REQUEST.",
            "default": "en",
            "example": "de"
          },
          "timestamp_type": {
            "type": "string",
            "description": "Akceptowana jest tylko wartość \"bitcoin\" (zakotwiczenie OpenTimestamps jest zawsze podejmowane). Każda inna wartość zwraca 400 INVALID_TIMESTAMP_TYPE. Aby uzyskać kwalifikowany znacznik czasu, użyj include_eidas.",
            "enum": [
              "bitcoin"
            ],
            "default": "bitcoin"
          },
          "include_eidas": {
            "type": "boolean",
            "description": "Zażądaj także kwalifikowanego elektronicznego znacznika czasu eIDAS (RFC 3161) nad SHA-256 pliku manifest.json, dostarczanego jako manifest.json.tsr (plus eidas_validation.json, gdy zwrócone zostaną dane walidacyjne). Tylko produkcyjne klucze API: testowy klucz API zwraca 403 EIDAS_REQUIRES_LIVE_KEY. Jeśli we wdrożeniu nie skonfigurowano usługi eIDAS, żądanie jest odrzucane z kodem 503 EIDAS_UNAVAILABLE, zanim cokolwiek zostanie naliczone. Przed użyciem tokenu ProofSnap go sprawdza (podpis, zawarty w nim skrót, łańcuch certyfikatów do głównego CA Disig QCA4, status certyfikatu TSA) i zapisuje wynik tej kontroli w eidas_validation.json (verification). Jeśli żądanie znacznika czasu nie powiedzie się w trakcie przechwycenia lub token nie przejdzie tej kontroli, pakiet jest dostarczany bez niego, tylko z zakotwiczeniem OpenTimestamps, znacznik nie jest naliczany, a timestamps.eidas (wiersz przechwycenia, GET /captures/{capture_id}, webhook completed) podaje status not_obtained z przyczyną (token_refused, unavailable lub time_budget). Od zrzutu ekranu opcjonalne kroki przechwycenia (treści WARC, odpowiedzi archiwum i WHOIS, podpisywanie C2PA, drugi odczyt zegara) zostawiają czas na znacznik i są obcinane jako pierwsze. Rozliczane za każdy znacznik.",
            "default": false
          },
          "include_rule_902": {
            "type": "boolean",
            "description": "Dodaj rule_902_certification.pdf: niepodpisany szablon certyfikacji według [FRE 902(13)/(14)](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-app-federalru-dup2-rule902.htm) i [28 U.S.C. 1746](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-partV-chap115-sec1746.htm) do uzupełnienia i podpisania przez osobę. Jego SHA-256 jest wymieniony w manifest.json. Jest też dodawany, gdy evidence_pack.custodian ma niepustą wartość. Jeśli jego wygenerowanie się nie powiedzie, przechwycenie kończy się bez niego. Obecnie wyłączone: szablon certyfikacji jest tymczasowo wyłączony, więc to pole jest akceptowane i ignorowane (rule_902_certification.pdf nie jest generowany ani wymieniany w pakiecie), a odpowiedź zawiera ostrzeżenie RULE902_DISABLED.",
            "default": false
          },
          "case_reference": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Oznaczenie Twojej sprawy. Białe znaki są scalane. Obowiązuje reguła zwykłego tekstu (zobacz PlainText). Zapisywane w metadata.json, forensic_log.json i chain_of_custody.json oraz drukowane w evidence.pdf.",
            "maxLength": 200
          },
          "case_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Identyfikator sprawy w Twoim systemie. Zapisywany tak samo jak case_reference; obowiązuje reguła zwykłego tekstu (zobacz PlainText).",
            "maxLength": 200
          },
          "case_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Notatka w formie dowolnego tekstu. Podlega regule zwykłego tekstu (zob. PlainText). Zapisywana w metadata.json, forensic_log.json i chain_of_custody.json (legal_context.note).",
            "maxLength": 500
          },
          "webhook_url": {
            "type": "string",
            "description": "Adres URL https, który otrzymuje capture.completed lub capture.failed. Zobacz Webhooki. Adres URL musi używać https i nie może wskazywać na adres prywatny, loopback, link-local, carrier-grade NAT ani adres metadanych chmury (te same reguły co dla adresu URL przechwycenia; w przeciwnym razie 422 SSRF_BLOCKED). Pusta wartość lub wartość złożona wyłącznie z białych znaków oznacza brak webhooka. Wymaga klucza API: wysłany z sesji w panelu zostaje odrzucony z 400 WEBHOOK_REQUIRES_API_KEY, ponieważ webhooki są podpisywane sekretem webhooka danego klucza.",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "maxProperties": 10,
            "description": "Do 10 tagów tekstowych (wartości do 500 znaków), zwracanych razem z przechwyceniem. Przekroczenie zwraca 400 METADATA_TOO_LARGE."
          },
          "evidence_pack": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "false pomija stronę certyfikacji w evidence.pdf.",
                "default": true
              },
              "jurisdiction": {
                "type": "string",
                "description": "Zapisywane w options w forensic_log.json. Nie ma innego wpływu.",
                "enum": [
                  "US-Federal",
                  "EU-eIDAS"
                ],
                "default": "US-Federal"
              },
              "custodian": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Akceptowane; nie jest drukowane w szablonie, którego pola deklaranta pozostają puste dla osoby, która go podpisuje.",
                    "maxLength": 200
                  },
                  "law_firm": {
                    "type": "string",
                    "description": "Akceptowane; nie jest drukowane w szablonie, którego pola deklaranta pozostają puste dla osoby, która go podpisuje.",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "description": "Akceptowane; nieużywane w szablonie.",
                    "maxLength": 200
                  },
                  "case_caption": {
                    "type": "string",
                    "description": "Akceptowane; nieużywane w szablonie.",
                    "maxLength": 200
                  }
                },
                "additionalProperties": false,
                "description": "Każda wartość podlega regule zwykłego tekstu (zob. PlainText); w przeciwnym razie 400 INVALID_EVIDENCE_PACK_OPTIONS."
              }
            },
            "description": "Nieprawidłowe wartości zwracają 400 INVALID_EVIDENCE_PACK_OPTIONS."
          }
        }
      },
      "CaptureRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Strona do przechwycenia. Tylko http lub https, najwyżej 2048 znaków. Adresy prywatne, loopback, link-local i adresy metadanych są odrzucane z 422 SSRF_BLOCKED, a nazwa hosta bez rekordów DNS zwraca 422 TARGET_UNREACHABLE.",
            "format": "uri",
            "maxLength": 2048
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "$ref": "#/components/schemas/PrivateNote"
          }
        },
        "example": {
          "url": "https://example.com",
          "options": {
            "case_reference": "Smith v. Jones",
            "webhook_url": "https://your-app.example/webhooks/proofsnap"
          }
        }
      },
      "CaptureAccepted": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string",
            "example": "cap_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing"
            ]
          },
          "url": {
            "type": "string"
          },
          "poll_path": {
            "type": "string",
            "description": "Ścieżka statusu wraz z etapem /v1, na przykład /v1/captures/cap_..."
          },
          "message": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "private_note": {
            "type": "string",
            "description": "Prywatna notatka w zapisanej (znormalizowanej) postaci, obecna tylko wtedy, gdy została wysłana."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "CaptureResult": {
        "type": "object",
        "description": "Przechwycenie, certyfikat, porównanie lub zaczernienie. Pola zależą od statusu.",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "url": {
            "type": "string",
            "description": "Przechwycony URL (null dla certyfikatów i porównań).",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "batch_id": {
            "type": "string",
            "nullable": true
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "type": {
            "type": "string",
            "description": "comparison dla porównań, import dla zaimportowanego pakietu dowodowego.",
            "enum": [
              "comparison",
              "import"
            ]
          },
          "compared": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Tylko w porównaniach: ID obu przechwyceń źródłowych."
          },
          "diff_summary": {
            "$ref": "#/components/schemas/DiffSummary"
          },
          "estimated_completion_seconds": {
            "type": "integer",
            "description": "Tylko dla processing: przybliżone oszacowanie, co najmniej 5."
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "CAPTURE_FAILED",
                  "CAPTURE_TIME_BUDGET_EXCEEDED",
                  "CAPTURE_SCREENSHOT_TIMEOUT",
                  "SSRF_BLOCKED",
                  "TARGET_UNREACHABLE",
                  "PAYMENT_REQUIRED",
                  "COMPARE_PROFILE_MISMATCH",
                  "COMPARE_URL_MISMATCH",
                  "RULE902_NOT_AVAILABLE_FOR_MISMATCH",
                  "COMPARE_SOURCE_INTEGRITY_FAILED",
                  "QUOTA_EXCEEDED",
                  "CERTIFY_FAILED",
                  "REDACTION_FAILED"
                ],
                "description": "Publiczny powód niepowodzenia (zobacz tabelę błędów). CAPTURE_SCREENSHOT_TIMEOUT: przeglądarka nie ukończyła zrzutu ekranu; CAPTURE_TIME_BUDGET_EXCEEDED: uruchomienie wyczerpało cały swój budżet czasu; żadne z nich nie jest rozliczane. Wiersz porównania (cmp_) może też zakończyć się błędem COMPARE_SOURCE_INTEGRITY_FAILED (pakiet źródłowy został zmieniony po przyjęciu żądania), COMPARE_PROFILE_MISMATCH lub COMPARE_URL_MISMATCH (wykryte dopiero w zapieczętowanych rekordach, które porównuje uruchomienie) albo RULE902_NOT_AVAILABLE_FOR_MISMATCH; jednostka jest zwracana. Certyfikacja skrótu uruchomiona w tle kończy się błędem QUOTA_EXCEEDED, gdy w chwili jej uruchomienia osiągnięto miesięczne maksimum. CERTIFY_FAILED i REDACTION_FAILED: nie udało się ukończyć certyfikacji skrótu lub zaczernienia; ponów żądanie."
              },
              "message": {
                "type": "string"
              }
            },
            "description": "Tylko failed."
          },
          "failed_at": {
            "type": "string",
            "description": "Tylko failed.",
            "format": "date-time"
          },
          "captured_at": {
            "type": "string",
            "description": "Kiedy strona została przechwycona (tylko zakończone wiersze). Dla zaczernienia czas przechwycenia jego przechwycenia źródłowego (redacted_at to chwila wykonania zaczernienia).",
            "format": "date-time"
          },
          "redacted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Tylko zaczernienia: kiedy wykonano zaczernienie. captured_at zaczernienia to czas wykonania jego przechwycenia źródłowego."
          },
          "compared_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Tylko porównania: kiedy wykonano porównanie. Jego captured_at zawiera tę samą wartość (zachowane dla starszych klientów); używaj compared_at."
          },
          "package_url": {
            "type": "string",
            "description": "Tylko completed: wstępnie podpisany link do pobrania pliku ZIP pakietu, ważny do 24 godzin (zobacz package_url_expires_at); aby otrzymać nowy link, odczytaj przechwycenie ponownie.",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "description": "Tylko completed: kiedy package_url przestanie działać, najwyżej 24 godziny po tej odpowiedzi (link podpisany tymczasowymi poświadczeniami serwera może przestać działać wcześniej); aby otrzymać nowy link, odczytaj przechwycenie ponownie.",
            "format": "date-time"
          },
          "verification_url": {
            "type": "string",
            "description": "Tylko completed: link do publicznego weryfikatora ProofSnap.",
            "format": "uri"
          },
          "manifest_hash": {
            "type": "string",
            "description": "Tylko completed: sha256:<hex> pliku manifest.json.",
            "example": "sha256:3f2a..."
          },
          "timestamps": {
            "$ref": "#/components/schemas/Timestamps"
          },
          "c2pa": {
            "type": "object",
            "properties": {
              "included": {
                "type": "boolean"
              },
              "signed_screenshot_sha256": {
                "type": "string",
                "description": "sha256:<hex> podpisanego zrzutu ekranu.",
                "nullable": true
              }
            },
            "description": "Tylko completed."
          },
          "file_count": {
            "type": "integer",
            "description": "Tylko completed."
          },
          "size_bytes": {
            "type": "integer",
            "description": "Tylko completed."
          },
          "warnings": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "consent_gated"
                  ]
                },
                "reason": {
                  "type": "string",
                  "nullable": true
                },
                "final_host": {
                  "type": "string",
                  "nullable": true
                },
                "final_url": {
                  "type": "string",
                  "nullable": true
                },
                "message": {
                  "type": "string"
                },
                "remedy": {
                  "type": "object",
                  "properties": {
                    "option": {
                      "type": "string"
                    },
                    "value": {
                      "type": "boolean"
                    }
                  }
                }
              }
            },
            "description": "Tylko completed, gdy przechwycenie trafiło na ekran zgody na pliki cookie lub stronę pośrednią zamiast na właściwą stronę. Uruchom ponownie z dismiss_consent: true."
          },
          "external_archive": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExternalArchive"
              }
            ],
            "description": "Tylko completed. Żądanie do Internet Archive złożone opcją external_archive: bieżący wynik (status), stan zapieczętowany w pakiecie (status_at_sealing) i link do migawki. { requested: false }, gdy żądanie nie zostało złożone."
          },
          "storage": {
            "$ref": "#/components/schemas/CaptureStorage"
          },
          "private_note": {
            "type": "string",
            "nullable": true,
            "description": "Twoja prywatna notatka (zobacz PrivateNote). Zwracana tylko autorowi przechwycenia; nigdy nie jest częścią pakietu dowodowego."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "Przechwycenie jest objęte blokadą prawną: dopóki wartość to true, nie jest usuwane (PATCH /captures/{capture_id} z action legal_hold). Blokada jest zdejmowana automatycznie, a przechwycenie usuwane, gdy subskrypcja się zakończyła i upłynęło jej 30 dni bez planu, który przechowuje dane."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Kiedy nałożono bieżącą blokadę prawną; null bez blokady."
          },
          "legal_hold_reason": {
            "type": "string",
            "nullable": true,
            "description": "Powód zapisany przy bieżącej blokadzie prawnej; null bez blokady lub bez powodu."
          },
          "legal_hold_set_by_email": {
            "type": "string",
            "nullable": true,
            "description": "Tylko właściciel firmy, przy przechwyceniu firmy objętym blokadą prawną: adres e-mail osoby, która nałożyła blokadę."
          },
          "legal_hold_source": {
            "type": "string",
            "nullable": true,
            "description": "Skąd pochodzi bieżąca blokada: \"manual\" (PATCH /captures/{capture_id}), \"bulk\" (POST /legal-holds), \"matter:<matter_id>\" (blokada prawna sprawy) lub \"derived:<capture_id>\" (zaczernienie lub porównanie zablokowane, ponieważ zablokowane jest przechwycenie, z którego powstało). null bez blokady."
          },
          "legal_hold_released_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Kiedy zdjęto ostatnią blokadę (początek okresu karencji po zdjęciu); null, dopóki blokada trwa lub nigdy nie została zdjęta, a także po automatycznym zdjęciu na koniec subskrypcji (bez okresu karencji)."
          },
          "deletion_blocked_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Koniec okresu ochronnego po zdjęciu blokady: do tego czasu przechwycenie nie jest trwale usuwane (trwałe DELETE 409 CAPTURE_HOLD_GRACE, bez opróżniania kosza, bez usuwania po okresie przechowywania); przeniesienie do kosza jest możliwe. Usunięcie konta i firmy po zakończeniu subskrypcji na to nie czeka. null, gdy nie trwa żaden okres ochronny (także gdy blokada jest nałożona: ta blokuje bez terminu końcowego)."
          },
          "s3_legal_hold": {
            "type": "object",
            "nullable": true,
            "description": "Blokada prawna S3 Object Lock przechowywanych obiektów pakietu (ZIP, zaktualizowany dowód OpenTimestamps i osobno przechowywana kopia, wszystkie wersje). status: on, off, pending (w trakcie nakładania), failed (serwer ponawia próbę), unsupported (magazyn nie ma konfiguracji Object Lock; blokada na rekordzie przechwycenia nadal obowiązuje), none (nie jest przechowywany żaden pakiet, np. przy nieudanym przechwyceniu).",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "on",
                  "off",
                  "pending",
                  "failed",
                  "unsupported",
                  "none"
                ]
              },
              "at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "source_held": {
            "type": "boolean",
            "nullable": true,
            "description": "Zaczernienia i porównania: true, gdy przechwycenie, z którego powstały, jest objęte blokadą prawną. Pakiet pochodny podlega blokadzie swojego źródła: dopóki źródło jest zablokowane, zaczernienie lub porównanie również jest zablokowane (legal_hold_source derived:<capture_id>), a jego blokady nie można zdjąć osobno (409 LEGAL_HOLD_SOURCE). null dla pozostałych przechwyceń."
          },
          "import": {
            "$ref": "#/components/schemas/CaptureImport"
          },
          "source": {
            "type": "string",
            "description": "Sposób wykonania przechwycenia: api, schedule, certify-hash, compare, redact, import. GET /captures/{capture_id} zwraca go tylko dla zaimportowanego pakietu (\"import\"); wiersze GET /captures zawsze go zawierają."
          }
        }
      },
      "Timestamps": {
        "type": "object",
        "properties": {
          "bitcoin_ots": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "Stan dowodu Bitcoin OpenTimestamps w manifest.json.ots. pending_confirmation: dowód czeka na blok Bitcoina (zwykle kilka godzin; sprawdzanie zaczyna się około 2 godziny po ukończeniu przechwycenia i jest powtarzane w coraz dłuższych odstępach). verified: blok Bitcoina poświadcza skrót manifestu (block_height, block_time). not_present: pakiet nie zawiera manifest.json.ots (podczas pieczętowania pakietu kalendarze były nieosiągalne). unconfirmed: w ciągu 7 dni sprawdzania nie znaleziono potwierdzenia; dowód w pakiecie nadal można zaktualizować i sprawdzić klientem OpenTimestamps (ots upgrade, ots verify). package_missing: pakietu dowodowego nie ma już w magazynie, więc dowodu nie można już sprawdzić. manifest_mismatch: manifest.json w przechowywanym pakiecie nie odpowiada skrótowi manifestu zapisanemu podczas pieczętowania; znacznik czasu nie jest dla niego potwierdzony. Oba stany są ostateczne. Sam pakiet nigdy nie jest zmieniany. not_checked: przechwycenie zostało ukończone, zanim wprowadzono automatyczne sprawdzanie potwierdzeń, i nigdy nie zostało sprawdzone; jego pakiet może jednak zawierać manifest.json.ots, który klient OpenTimestamps potrafi zaktualizować i zweryfikować. Webhooki i odpowiedzi na utworzenie są wysyłane w momencie ukończenia, więc podają pending_confirmation (lub not_present, gdy kalendarze zawiodły); aktualny stan odczytasz przez GET /captures/{capture_id}. Pakiet jest pieczętowany z oczekującym dowodem: w tym momencie skrót manifestu został już przesłany do kalendarzy OpenTimestamps. Po potwierdzeniu w bloku Bitcoina {block_height} ({block_time} UTC) dowód wykazuje, że manifest.json istniał najpóźniej w tym czasie; pobierz zaktualizowany dowód (upgraded_proof_url) i umieść go obok manifest.json jako manifest.json.upgraded.ots, a verify.sh i verify.ps1 sprawdzą go automatycznie.",
                "enum": [
                  "pending_confirmation",
                  "verified",
                  "not_present",
                  "unconfirmed",
                  "package_missing",
                  "manifest_mismatch",
                  "not_checked"
                ]
              },
              "block_height": {
                "type": "integer",
                "nullable": true,
                "description": "Wysokość bloku Bitcoin, który poświadcza dowód. Ustawiana, gdy status ma wartość verified."
              },
              "block_time": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Czas tego bloku Bitcoina. Tylko GET /captures/{capture_id}, ustawiane, gdy status ma wartość verified."
              },
              "verified_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Kiedy ProofSnap potwierdził zakotwiczenie. Tylko GET /captures/{capture_id}, ustawiane, gdy status ma wartość verified."
              },
              "upgraded_proof_url": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Tylko GET /captures/{capture_id}. Po zweryfikowaniu zakotwiczenia ProofSnap zapisuje dowód OpenTimestamps zaktualizowany z kalendarzy (zawiera atestację bloku Bitcoina, więc weryfikuje się offline bez kontaktu z żadnym kalendarzem). To jest wstępnie podpisany link do jego pobrania, ważny do 24 godzin, tak jak package_url; aby uzyskać nowy link, odczytaj przechwycenie ponownie. null, gdy go nie ma (jeszcze nie zweryfikowano zakotwiczenia lub dowód zostanie zapisany później przez uzupełnianie wsteczne w tle). Zapieczętowany pakiet nigdy nie jest zmieniany: zapisz plik obok manifest.json pakietu jako manifest.json.upgraded.ots. Pakiety zapieczętowane od 2026-09-29 weryfikują go dołączonymi skryptami (verification/verify.sh i verify.ps1 używają go zamiast manifest.json.ots); dla starszego pakietu uruchom `ots verify manifest.json.upgraded.ots -f manifest.json`. Eksporty spraw zawierają go jako captures/<capture_id>.upgraded.ots obok captures/<capture_id>.zip. Usuwany razem z pakietem."
              }
            }
          },
          "eidas": {
            "$ref": "#/components/schemas/EidasTimestamp"
          }
        }
      },
      "DiffSummary": {
        "type": "object",
        "properties": {
          "text_insertions": {
            "type": "integer",
            "description": "Dodane wiersze (ta sama wartość co text_lines_added; od 2026-10-14 tekst jest liczony w wierszach, zob. text_unit). Porównania wykonane wcześniej liczą znaki. Przestarzałe (2026-10-14): zachowane dla istniejących odbiorców, nie usunięte; użyj text_lines_added.",
            "deprecated": true
          },
          "text_deletions": {
            "type": "integer",
            "description": "Usunięte wiersze (ta sama wartość co text_lines_removed; od 2026-10-14 wiersze, wcześniej znaki). Przestarzałe (2026-10-14): zachowane dla istniejących odbiorców, nie usunięte; użyj text_lines_removed.",
            "deprecated": true
          },
          "text_unit": {
            "type": "string",
            "enum": [
              "line"
            ],
            "description": "Jednostka liczników tekstu: line (każdy wiersz domtextcontent.txt wraz ze znakiem końca wiersza). Brak w porównaniach wykonanych przed 2026-10-14, które liczyły znaki."
          },
          "text_lines_added": {
            "type": "integer",
            "description": "Dodane wiersze (wiersze tekstu Po w dodanych fragmentach porównania wiersz po wierszu)."
          },
          "text_lines_removed": {
            "type": "integer",
            "description": "Usunięte wiersze (wiersze tekstu Przed w usuniętych fragmentach porównania wiersz po wierszu)."
          },
          "text_lines_before": {
            "type": "integer",
            "description": "Wiersze tekstu Przed."
          },
          "text_lines_after": {
            "type": "integer",
            "description": "Wiersze tekstu Po."
          },
          "text_changed_percent": {
            "type": "number",
            "description": "100 x (usunięte wiersze + dodane wiersze) / (wiersze przed + wiersze po), z dokładnością do jednego miejsca po przecinku."
          },
          "text_diff_capped": {
            "type": "boolean",
            "description": "true, gdy porównanie wierszy wyczerpało budżet obliczeniowy i część tekstów została policzona jako zastąpiona w całości (zawyża wynik, nigdy nie ukrywa zmiany)."
          },
          "metadata_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "{ key, before, after, class } dla każdego pola metadata.json, które się różni, dokładnie tych: url, final_url, page_title, http_status, http_headers.content-type, http_headers.server, dns.connected_ip, resolved_ips (porównywane jako posortowana lista), tls.issuer, tls.valid_to oraz pól profilu urządzenia viewport_width, viewport_height, device_scale_factor, is_mobile i user_agent. class to content (url, final_url, page_title, http_status, http_headers.content-type: liczone jako zmiana), infrastructure (http_headers.server, dns.connected_ip, resolved_ips, tls.issuer, tls.valid_to: który serwer dostarczył stronę, tylko raportowane) lub device_profile (jak przechwycenie wyrenderowało stronę, tylko raportowane). Porównanie wykonane przed 2026-10-10 nie ma class."
          },
          "meta_content": {
            "type": "integer",
            "description": "Różniące się pola metadanych klasy content; wliczają się do changed."
          },
          "meta_infra": {
            "type": "integer",
            "description": "Różniące się pola metadanych klasy infrastructure (oprogramowanie serwera, adres IP połączenia, rozwiązane adresy IP, wystawca i termin ważności certyfikatu TLS). Sieć dostarczania treści odpowiada z rotujących adresów, więc dwa wczytania niezmienionej strony mogą się tu różnić; podawane informacyjnie, nigdy niewliczane do changed."
          },
          "meta_device_profile": {
            "type": "integer",
            "description": "Różniące się pola profilu urządzenia (okno widoku, skala, emulacja urządzenia mobilnego, user agent); podawane informacyjnie, nigdy niewliczane do changed."
          },
          "image_changes": {
            "type": "object",
            "description": "Porównanie obrazów na podstawie spisu obrazów zapieczętowanego przez każde przechwycenie źródłowe (image_inventory.json). status ran albo not_run z reason source_without_inventory (źródło przechwycone, zanim przechwycenia zaczęły zapisywać spis), source_inventory_failed lub source_inventory_unreadable; nic innego nie jest używane w jego miejsce. Pełne wyniki dla poszczególnych obrazów znajdują się w image_changes.json pakietu.",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ran",
                  "not_run"
                ]
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "source_without_inventory",
                  "source_inventory_failed",
                  "source_inventory_unreadable",
                  null
                ],
                "description": "Dlaczego obrazy nie zostały porównane: source_without_inventory (źródło wykonane, zanim przechwycenia zaczęły zapisywać spis obrazów; nowe przechwycenia go zapisują), source_inventory_failed (nie udało się zapisać spisu przy wykonywaniu tego przechwycenia) lub source_inventory_unreadable; null, gdy porównanie zostało wykonane."
              },
              "images_changed": {
                "type": "integer",
                "nullable": true,
                "description": "Własne obrazy witryny (pobierane z rejestrowalnej domeny samej strony albo, od 2026-10-14, z domeny, którą strona deklaruje jako własny og:image lub ikonę, albo która w obu przechwyceniach dostarcza większość obrazów strony: image_changes.json site_image_hosts) zastąpione, dodane lub usunięte, czyli obrazowa część wartości changed; przeniesione obrazy nie są uwzględniane. null, gdy porównanie nie zostało wykonane. Przestarzałe (2026-10-14): zachowane dla istniejących odbiorców, nie usunięte; użyj site_images_changed.",
                "deprecated": true
              },
              "first_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "third_party": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "tracking_pixel": {
                "$ref": "#/components/schemas/ImageChangeCounts"
              },
              "inventories_truncated": {
                "type": "boolean",
                "description": "true, gdy spis źródła osiągnął limit wpisów; zob. inventory_cutoff."
              },
              "site_images_changed": {
                "type": "integer",
                "description": "To samo co images_changed (liczba)."
              },
              "other_images_changed": {
                "type": "integer",
                "description": "Obrazy z innych domen i piksele śledzące zastąpione, usunięte lub dodane (nigdy nie są liczone)."
              },
              "site_image_hosts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Domeny rejestrowalne uznawane za własne domeny witryny oprócz domeny strony (site_image_hosts w image_changes.json)."
              },
              "inventory_cutoff": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "before_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Obraz występujący tylko w Before jest rozstrzygalny (removed), gdy jego indeks w spisie obrazów Before jest mniejszy od tej wartości; null: bez limitu (spis obrazów After jest kompletny)."
                  },
                  "after_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Obraz występujący tylko w After jest rozstrzygalny (added), gdy jego indeks w spisie obrazów After jest mniejszy od tej wartości; null: bez limitu (spis obrazów Before jest kompletny)."
                  }
                },
                "description": "Od drugiego audytu z 2026-10-14 (algorytm 3 w image_changes.json) obiekt, rozstrzygany według indeksu w dokumencie: gdy DRUGI spis osiągnął swój limit pozycji, obraz znaleziony tylko po jednej stronie można rozstrzygnąć tylko wtedy, gdy jego indeks jest mniejszy niż liczba pozycji drugiego spisu pomniejszona o margines (większa z wartości: 10 pozycji i 5 procent tej liczby); w przeciwnym razie jest nierozstrzygnięty (undetermined_reason beyond_inventory_limit). null, gdy żaden spis nie jest obcięty. Porównania wykonane wcześniej zawierają liczbę (względną pozycję w dokumencie według poprzedniej reguły)."
              }
            }
          },
          "changed": {
            "type": "boolean",
            "nullable": true,
            "description": "true, gdy różni się widoczny tekst, różni się pole metadanych treści (meta_content) albo obraz z domeny strony został zastąpiony, usunięty lub dodany (image_changes.images_changed). Pola infrastruktury i profilu urządzenia, przeniesione obrazy (te same bajty pod innym adresem), obrazy podmiotów zewnętrznych i piksele śledzące są raportowane, ale nie ustawiają tej wartości. null (od 2026-10-14), gdy nic, co się liczy, nie zmieniło się w sposób pewny, ale wyniku dla tekstu lub obrazów nie da się ustalić (przechwycenia wyrenderowane inaczej, allow_profile_mismatch). Pakiet podaje regułę w comparison_method.json counted_as_change. Przestarzałe (2026-10-14): pozostawione dla istniejących odbiorców, nie zostanie usunięte; użyj verdict (changed | unchanged | undetermined) oraz cząstkowych text_verdict, images_verdict, metadata_verdict.",
            "deprecated": true
          },
          "text_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "undetermined, gdy oba przechwycenia zostały wyrenderowane z innym profilem urządzenia lub innymi ustawieniami przechwytywania (allow_profile_mismatch)."
          },
          "images_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined",
              "not_run"
            ]
          },
          "metadata_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged"
            ],
            "description": "Z pól metadanych klasy treści (adres, adres końcowy, tytuł, status HTTP, typ zawartości)."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "Ogólny wynik słownie: changed, gdy changed ma wartość true, undetermined, gdy changed ma wartość null."
          },
          "undetermined_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "render_profile_mismatch",
              "capture_settings_mismatch",
              null
            ]
          },
          "text_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Tylko GET: do 60 zmienionych fragmentów tekstu."
          },
          "change_regions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Tylko GET: do 60 zmian tekstu zlokalizowanych na zrzutach ekranu (na podstawie text_layout.json źródeł), w pikselach zrzutu ekranu. Każda zmiana zawiera counterpart_y_px (jej położenie w pionie na drugim zrzucie ekranu, przeniesione z najbliższego niezmienionego bloku tekstu) oraz report_region (numer miejsca zmiany, w którym pokazuje ją evidence.pdf, maksymalnie 10 miejsc, lub null), jak w change_regions.json."
          },
          "change_regions_info": {
            "type": "object",
            "nullable": true
          },
          "order_reversed": {
            "type": "boolean",
            "description": "Tylko GET: true, gdy przechwycenie podane jako capture_id_1 (Przed) zostało wykonane później niż przechwycenie podane jako capture_id_2 (Po), jak order_reversed w compare.json. Porównanie zachowuje podaną kolejność; raport i panel o tym informują."
          },
          "render_profile_mismatch": {
            "type": "boolean",
            "description": "Tylko GET: oba przechwycenia wyrenderowano z różnymi profilami urządzenia (compare.json)."
          },
          "capture_settings_mismatch": {
            "type": "boolean",
            "description": "Tylko GET: oba przechwycenia wykonano z różnymi ustawieniami przechwytywania (compare.json)."
          },
          "capture_settings_differing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "setting": {
                  "type": "string"
                },
                "before": {},
                "after": {}
              }
            },
            "description": "Tylko GET: ustawienia, które się różnią, { setting, before, after }."
          },
          "profile_mismatch_allowed": {
            "type": "boolean",
            "description": "Tylko GET: porównanie wykonano z allow_profile_mismatch dla przechwyceń wyrenderowanych w różny sposób. Przestarzałe (2026-10-14): zachowane dla istniejących odbiorców, nie usunięte; użyj render_profile_mismatch i capture_settings_mismatch.",
            "deprecated": true
          },
          "url_mismatch": {
            "type": "boolean",
            "description": "Tylko GET: oba przechwycenia nie mają wspólnego adresu (wykonano z allow_url_mismatch)."
          }
        },
        "description": "Podsumowanie porównania."
      },
      "ImageChangeCounts": {
        "type": "object",
        "properties": {
          "unchanged": {
            "type": "integer"
          },
          "replaced": {
            "type": "integer"
          },
          "moved": {
            "type": "integer"
          },
          "removed": {
            "type": "integer"
          },
          "added": {
            "type": "integer"
          },
          "undetermined": {
            "type": "integer"
          },
          "alt_changed": {
            "type": "integer"
          },
          "position_changed": {
            "type": "integer"
          },
          "undetermined_same_error": {
            "type": "integer",
            "description": "Nierozstrzygnięte pary, których obie strony zwróciły ten sam kod błędu HTTP (na przykład 404 w obu przechwyceniach); wliczane do undetermined, uwzględniane w raporcie i niewymieniane."
          },
          "occurrences": {
            "type": "integer",
            "description": "Wszystkie wyniki tej strony: wystąpienia na stronie."
          },
          "distinct": {
            "type": "object",
            "description": "Liczba różnych obrazów wśród wyników, łącznie i według kategorii (obraz wyświetlany kilka razy liczy się raz): każdy wynik reprezentuje SHA-256 swojej strony Po, a gdy jej nie ma, strony Przed, lub adres tej strony, gdy nie ma ona SHA-256.",
            "properties": {
              "total": {
                "type": "integer"
              },
              "unchanged": {
                "type": "integer"
              },
              "replaced": {
                "type": "integer"
              },
              "moved": {
                "type": "integer"
              },
              "removed": {
                "type": "integer"
              },
              "added": {
                "type": "integer"
              },
              "undetermined": {
                "type": "integer"
              }
            }
          }
        },
        "description": "Liczby według kategorii, liczone jako wystąpienia na stronie (jeden obraz wyświetlony trzy razy liczy się trzy razy): unchanged, replaced (ten sam adres, inne bajty), moved (inny adres, te same bajty; zgłaszane, ale nieliczone jako zmiana), removed, added, undetermined (obraz bez skrótu SHA-256 po żadnej ze stron, nigdy nieliczony jako usunięty ani dodany); ponadto sparowane obrazy, których tekst alternatywny lub miejsce wśród obrazów strony się zmieniły, nierozstrzygnięte pary z tym samym błędem HTTP po obu stronach oraz unikalne obrazy."
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048
            },
            "minItems": 2,
            "maxItems": 50,
            "description": "Od 2 do 50 adresów URL, każdy walidowany tak samo jak CaptureRequest.url. Przy przyjęciu partii nazwy hostów nie są sprawdzane w DNS; URL, który nie przejdzie kontroli DNS lub adresu, kończy się później błędem ze statusem failed (i zdarzeniem capture.failed)."
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PrivateNote"
              }
            ],
            "description": "Jedna prywatna notatka zapisywana przy każdym przechwyceniu partii. Twoja prywatna notatka do przechwycenia (np. \"mobile capture\"), najwyżej 200 znaków, zwykły tekst (reguła PlainText). Jest zapisywana tylko w rekordzie przechwycenia: NIE jest częścią zapieczętowanego pakietu dowodowego (nie ma jej w ZIP, PDF, manifeście ani w żadnym skrócie) i nie ma wartości dowodowej, więc można ją później zmienić lub usunąć przez PATCH /v1/captures/{capture_id} bez wpływu na dowody. Odczytać ją może tylko autor przechwycenia. Jeśli nie chcesz notatki, pomiń pole albo wyślij null lub pusty ciąg znaków."
          }
        }
      },
      "BatchResult": {
        "type": "object",
        "properties": {
          "batch_id": {
            "type": "string",
            "example": "batch_a1B2c3D4e5F6g7H8"
          },
          "count": {
            "type": "integer"
          },
          "captures": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "capture_id": {
                  "type": "string"
                },
                "url": {
                  "type": "string"
                },
                "status": {
                  "type": "string",
                  "description": "failed, gdy nie udało się uruchomić przechwycenia.",
                  "enum": [
                    "processing",
                    "failed"
                  ]
                }
              }
            }
          },
          "not_admitted": {
            "type": "array",
            "description": "Tylko gdy konto osiągnęło limit jednocześnie przetwarzanych przechwyceń: adresy URL, których nie uruchomiono, w kolejności wysłania. Nic nie kosztują (nie zużywają limitu liczby żądań ani jednostki miesięcznej); wyślij je ponownie później. Brak tego pola, gdy uruchomiono wszystkie adresy URL.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "description": "Dlaczego przechwycenie adresu URL nie zostało uruchomione.",
                  "enum": [
                    "CONCURRENT_CAPTURE_LIMIT"
                  ]
                }
              }
            }
          },
          "in_flight_limit": {
            "type": "integer",
            "description": "Tylko razem z not_admitted: liczba przechwyceń, które konto może przetwarzać jednocześnie (developer 10, startup 25, growth 100, legal_solo i legal_seats 25, custom 200, website_archive 25, sandbox 5)."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "VerifyResult": {
        "type": "object",
        "properties": {
          "verdict": {
            "type": "string",
            "nullable": true,
            "description": "match: podpis jest prawidłowy, a każdy wymieniony plik jest obecny z zapisanym skrótem SHA-256. modified: podpis jest nieprawidłowy albo brakuje pliku lub plik został zmieniony. invalid: nie udało się sprawdzić pakietu. null tylko dla zaimportowanego pakietu, który tym razem nie został ponownie sprawdzony (`rechecked: false`, z `reason` i weryfikacją zapisaną przy imporcie w `stored_verification`).",
            "enum": [
              "match",
              "modified",
              "invalid",
              null
            ]
          },
          "reason": {
            "type": "string",
            "description": "Tylko dla invalid; w przypadku zaimportowanego pakietu z `rechecked: false` powód, dla którego nie został ponownie sprawdzony (too_large_for_synchronous_check, time_budget, storage_error)."
          },
          "capture_id": {
            "type": "string"
          },
          "source": {
            "type": "string",
            "enum": [
              "s3"
            ]
          },
          "checks": {
            "type": "object",
            "properties": {
              "manifest_signature": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "valid",
                      "invalid"
                    ]
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "RSA-4096"
                    ]
                  }
                }
              },
              "declared_files": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "all_match",
                      "mismatch"
                    ]
                  },
                  "mismatched": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "missing": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Tylko obecność manifest.json.ots; sam dowód nie jest tu sprawdzany.",
                    "enum": [
                      "present",
                      "not_present"
                    ]
                  }
                }
              },
              "eidas_disig": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Obecność manifest.json.tsr. Sam token jest sprawdzany w checks.eidas. missing: manifest.json zapisuje eidas_requested (pakiet został wydany z tokenem), ale w pakiecie nie ma manifest.json.tsr.",
                    "enum": [
                      "present",
                      "missing"
                    ]
                  }
                },
                "nullable": true,
                "description": "null, gdy w pakiecie nie ma manifest.json.tsr, a manifest.json nie zawiera eidas_requested."
              },
              "proofsnap_issuance": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "issued_by_proofsnap: skrót manifestu znajduje się w rejestrze wydań ProofSnap. unverified: nie ma go tam (pakietów wydanych przed powstaniem rejestru w nim nie ma). unknown: wyszukiwanie nie powiodło się.",
                    "enum": [
                      "issued_by_proofsnap",
                      "unverified",
                      "unknown"
                    ]
                  },
                  "manifest_sha256": {
                    "type": "string"
                  },
                  "issued_at": {
                    "type": "string",
                    "nullable": true,
                    "format": "date-time"
                  },
                  "type": {
                    "type": "string"
                  }
                }
              },
              "eidas": {
                "type": "object",
                "nullable": true,
                "description": "null, gdy w pakiecie nie ma manifest.json.tsr, a manifest.json nie zawiera eidas_requested. Token znacznika czasu eIDAS sprawdzony przez ProofSnap na żądanie na podstawie przechowywanego pakietu. Jest to kontrola wykonana przez ProofSnap; pole note opisuje, jak samodzielnie zweryfikować token.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "verified_revocation_unknown",
                      "failed",
                      "unavailable",
                      "missing"
                    ],
                    "description": "missing: manifest.json zawiera eidas_requested: true, więc pakiet został wydany z kwalifikowanym znacznikiem czasu, ale nie ma w nim manifest.json.tsr (odpowiedź zawiera wtedy ostrzeżenie EIDAS_TOKEN_MISSING). verified: wszystkie kontrole przeszły pomyślnie, a status certyfikatu TSA zapisany przy wydaniu jest znany (good albo revoked po czasie tokenu z powodu innego niż przejęcie klucza). verified_revocation_unknown: wszystkie wykonane kontrole przeszły pomyślnie, ale statusu certyfikatu TSA nie dało się ustalić na podstawie zapisanych danych (revocation.status unknown lub not_checked). failed: co najmniej jedna kontrola nie powiodła się, zob. failures (odpowiedź zawiera wtedy ostrzeżenie EIDAS_TOKEN_FAILED). unavailable: brak werdyktu dla tokenu (reason: not configured, timeout, input_rejected, gdy weryfikator odrzucił dane wejściowe, np. zbyt duże dane walidacyjne, inconclusive); nie wynika z tego nic na temat tokenu."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Tylko dla unavailable: timeout, input_rejected, inconclusive lub krótkie wyjaśnienie."
                  },
                  "checked_by": {
                    "type": "string",
                    "example": "ProofSnap API on request"
                  },
                  "checked_at": {
                    "type": "string",
                    "format": "date-time"
                  },
                  "gen_time": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "Czas w tokenie (TSTInfo genTime)."
                  },
                  "tsa_name": {
                    "type": "string",
                    "nullable": true,
                    "description": "Z certyfikatu podpisującego (nazwa pospolita i organizacja), nigdy wartość domyślna."
                  },
                  "serial_number": {
                    "type": "string",
                    "nullable": true
                  },
                  "policy_oid": {
                    "type": "string",
                    "nullable": true
                  },
                  "hash_algorithm": {
                    "type": "string",
                    "nullable": true
                  },
                  "signature_algorithm": {
                    "type": "string"
                  },
                  "signing_certificate_form": {
                    "type": "string",
                    "enum": [
                      "ESSCertIDv2",
                      "ESSCertID"
                    ]
                  },
                  "signer_certificate": {
                    "type": "object",
                    "nullable": true,
                    "properties": {
                      "subject": {
                        "type": "string"
                      },
                      "issuer": {
                        "type": "string"
                      },
                      "serial_number": {
                        "type": "string"
                      },
                      "sha256": {
                        "type": "string"
                      },
                      "not_before": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "not_after": {
                        "type": "string",
                        "format": "date-time"
                      }
                    }
                  },
                  "root": {
                    "type": "object",
                    "properties": {
                      "label": {
                        "type": "string",
                        "example": "CA Disig QCA4"
                      },
                      "sha256": {
                        "type": "string"
                      }
                    }
                  },
                  "chain_checked_at": {
                    "type": "string",
                    "format": "date-time",
                    "nullable": true,
                    "description": "Chwila, na którą sprawdzono łańcuch: genTime tokenu, dzięki czemu certyfikat, który wygasł po wydaniu tokenu, nie powoduje negatywnego wyniku."
                  },
                  "chain": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "sha256": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "revocation": {
                    "type": "object",
                    "description": "Status certyfikatu TSA na podstawie danych zapisanych przy wydaniu (basis recorded_at_issuance), zweryfikowany podpisem, przy czym certyfikat musiał być aktualny w chwili genTime. Nie jest pobierany na bieżąco.",
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "good",
                          "revoked",
                          "revoked_after_gen_time",
                          "unknown",
                          "not_checked"
                        ],
                        "description": "revoked_after_gen_time: unieważniony po czasie tokenu z powodu innego niż przejęcie klucza (token pozostaje zweryfikowany). not_checked: nie zapisano ani nie sprawdzono żadnych danych o unieważnieniu."
                      },
                      "source": {
                        "type": "string",
                        "enum": [
                          "ocsp",
                          "crl"
                        ],
                        "nullable": true
                      },
                      "basis": {
                        "type": "string",
                        "enum": [
                          "recorded_at_issuance"
                        ]
                      },
                      "produced_at": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "this_update": {
                        "type": "string",
                        "format": "date-time"
                      },
                      "next_update": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                      },
                      "reason": {
                        "type": "string",
                        "description": "Dlaczego status jest nieznany."
                      }
                    }
                  },
                  "checks": {
                    "type": "object",
                    "description": "Każda kontrola: passed, failed, not_applicable (nonce: brak zapisanego dla przechowywanego tokenu), unknown (unieważnienie bez użytecznych danych) lub not_checked (wcześniejsze niepowodzenie przerwało kontrolę).",
                    "properties": {
                      "response_status": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_imprint": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "nonce": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "content_type": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "message_digest": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signature": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "signing_certificate": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "timestamping_eku": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "chain": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      },
                      "revocation": {
                        "type": "string",
                        "enum": [
                          "passed",
                          "failed",
                          "not_applicable",
                          "unknown",
                          "not_checked"
                        ]
                      }
                    }
                  },
                  "failures": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "check": {
                          "type": "string"
                        },
                        "reason": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "note": {
                    "type": "string"
                  },
                  "detail": {
                    "type": "string",
                    "nullable": true,
                    "description": "Tylko input_rejected: co odrzucił weryfikator."
                  }
                }
              }
            },
            "description": "Brak, gdy verdict ma wartość invalid. Werdykt zależy wyłącznie od manifest_signature i declared_files; eidas jest raportowany obok niego, a gdy weryfikacja się nie powiodła, także w warnings."
          },
          "warnings": {
            "type": "array",
            "description": "Występuje tylko wtedy, gdy coś poza werdyktem wymaga uwagi. EIDAS_TOKEN_FAILED: manifest.json.tsr nie przeszedł sprawdzenia w checks.eidas. EIDAS_TOKEN_MISSING: manifest.json zapisuje eidas_requested, ale w pakiecie nie ma manifest.json.tsr.",
            "items": {
              "type": "object",
              "properties": {
                "code": {
                  "type": "string",
                  "enum": [
                    "EIDAS_TOKEN_FAILED",
                    "EIDAS_TOKEN_MISSING"
                  ]
                },
                "message": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "ScheduleRequest": {
        "type": "object",
        "required": [
          "url",
          "mode"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Strona do przechwycenia, walidowana tak samo jak CaptureRequest.url.",
            "format": "uri",
            "maxLength": 2048
          },
          "mode": {
            "type": "string",
            "description": "interval: co interval_seconds. cron: zgodnie z cron_expr. on_change: sprawdzaj stronę co interval_seconds i przechwyć ją tylko wtedy, gdy zmienił się jej tekst.",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "description": "Wymagane dla interval i on_change. Co najmniej minimum planu (300 w developer, startup, growth, custom, legal_solo i legal_seats; 86400 w website_archive), w przeciwnym razie 400 INTERVAL_TOO_SHORT.",
            "minimum": 300
          },
          "cron_expr": {
            "type": "string",
            "description": "Wymagane dla cron (maksymalnie 200 znaków). Wyrażenie, którego nie da się przetworzyć, zwraca 400 INVALID_CRON; dwa kolejne uruchomienia bliżej siebie niż minimum planu zwracają 400 INTERVAL_TOO_SHORT.",
            "maxLength": 200,
            "example": "0 9 * * 1-5"
          },
          "timezone": {
            "type": "string",
            "description": "Strefa czasowa IANA używana do obliczania cron_expr. Domyślnie UTC.",
            "example": "Europe/Berlin"
          },
          "webhook_url": {
            "type": "string",
            "description": "Adres URL https. Uruchomienia interval i cron wysyłają capture.completed lub capture.failed; on_change wysyła monitor.change_detected. Adres URL musi używać https i nie może wskazywać na adres prywatny, loopback, link-local, carrier-grade NAT ani adres metadanych chmury (te same reguły co dla adresu URL przechwycenia; w przeciwnym razie 422 SSRF_BLOCKED). Pusta wartość lub wartość złożona wyłącznie z białych znaków oznacza brak webhooka. Automatyczne przechwycenie ma jeden webhook_url, na najwyższym poziomie: options.webhook_url nie jest zapisywany (gdy nie wysłano webhook_url na najwyższym poziomie, zostaje użyty jako on), więc automatyczne przechwycenie on_change nigdy nie wysyła dodatkowo capture.completed. Wymaga klucza API: wysłany z sesji w panelu zostaje odrzucony z 400 WEBHOOK_REQUIRES_API_KEY, ponieważ webhooki są podpisywane sekretem webhooka danego klucza.",
            "format": "uri"
          },
          "js_render": {
            "type": "boolean",
            "description": "Tylko dla on_change: sprawdzaj stronę przez pełne renderowanie w przeglądarce zamiast zwykłego pobrania przez HTTP (dla stron, które wymagają JavaScriptu).",
            "default": false
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true,
            "minimum": 86400,
            "maximum": 34560000,
            "description": "Tylko dla on_change: wykonaj także pełne przechwycenie (wysyła capture.completed), gdy najnowsze przechwycenie tego automatycznego przechwycenia jest starsze niż ta wartość, nawet jeśli strona się nie zmieniła. Dla przepisów wymagających kopii co najmniej co N dni, np. 7776000 dla 90 dni. Od 1 dnia do 400 dni i dłużej niż interval_seconds; null (domyślnie) przechwytuje tylko przy zmianie. PATCH z null usuwa to ustawienie."
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "Tylko dla on_change (2026-10-14): która wykryta zmiana wysyła monitor.change_detected. any (domyślnie, dotychczasowe zachowanie): każde przechwycenie przy zmianie. text: tylko gdy porównanie z poprzednim przechwyceniem wykaże zmianę widocznego tekstu (diff_summary.text_verdict changed); przechwycenie przy zmianie, którego porównanie wskazuje, że tekst się nie zmienił lub nie da się tego ustalić, nic nie wysyła (przechwycenie i tak jest wykonywane, rozliczane i wysyła capture.completed do subskrypcji). Bez wyniku porównania (brak punktu odniesienia, porównanie nie powiodło się) alert jest wysyłany. 400 MALFORMED_REQUEST przy innym trybie lub wartości. null dla harmonogramów interval i cron."
          }
        },
        "example": {
          "url": "https://example.com/terms",
          "mode": "on_change",
          "interval_seconds": 3600,
          "webhook_url": "https://your-app.example/webhooks/proofsnap"
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "schedule_id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "nullable": true
          },
          "cron_expr": {
            "type": "string",
            "nullable": true
          },
          "timezone": {
            "type": "string",
            "nullable": true
          },
          "webhook_url": {
            "type": "string",
            "nullable": true,
            "description": "Webhook automatycznego przechwycenia (dla starszego automatycznego przechwycenia, które przechowywało go w options.webhook_url, ta wartość jest pokazywana tutaj, a options nigdy jej nie zawiera). Wymaga klucza API: wysłany z sesji w panelu zostaje odrzucony z 400 WEBHOOK_REQUIRES_API_KEY, ponieważ webhooki są podpisywane sekretem webhooka danego klucza."
          },
          "options": {
            "type": "object",
            "description": "Opcje przechwycenia z zastosowanymi wartościami domyślnymi."
          },
          "js_render": {
            "type": "boolean"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "paused"
            ]
          },
          "next_run_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_run_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time"
          },
          "last_capture_id": {
            "type": "string",
            "nullable": true
          },
          "run_count": {
            "type": "integer"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "pause_reason": {
            "type": "string",
            "description": "Dlaczego automatyczne przechwycenie zostało wstrzymane automatycznie: max_failures (5 kolejnych nieudanych uruchomień), no_billing, plan_downgrade (limity planu lub plan Archive only), invalid_cron lub key_revoked. null, gdy jest aktywne lub zostało wstrzymane przez Ciebie.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true
          },
          "current_capture_id": {
            "type": "string",
            "nullable": true,
            "description": "Najnowsze ZAKOŃCZONE przechwycenie automatycznego przechwycenia (jego aktualna wersja; last_capture_id to najnowsze uruchomione)."
          },
          "current_captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "on_change: any lub text (any dla automatycznego przechwycenia utworzonego, zanim ta opcja istniała); null dla interval i cron."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "Matter": {
        "type": "object",
        "properties": {
          "matter_id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "client": {
            "type": "string",
            "nullable": true
          },
          "case_number": {
            "type": "string",
            "nullable": true
          },
          "opposing_party": {
            "type": "string",
            "nullable": true
          },
          "jurisdiction": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "archived",
              "sealed"
            ]
          },
          "sealed": {
            "type": "boolean"
          },
          "capture_count": {
            "type": "integer"
          },
          "custody_count": {
            "type": "integer",
            "description": "Wpisy w dzienniku łańcucha dowodowego."
          },
          "custody_head_hash": {
            "type": "string",
            "description": "sha256:<hex> najnowszego wpisu dziennika łańcucha dowodowego.",
            "nullable": true
          },
          "seal": {
            "type": "object",
            "nullable": true,
            "description": "null do momentu zapieczętowania.",
            "allOf": [
              {
                "$ref": "#/components/schemas/MatterSeal"
              }
            ]
          },
          "member_uids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_by": {
            "type": "string",
            "description": "Właściciel.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "read_only": {
            "type": "boolean",
            "description": "Występuje (true) w sprawie firmy, która zakończyła działalność lub jest przechowywana jako archiwum firmy: tylko otwieranie, eksport i unieważnienie udostępnienia."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "Blokada prawna sprawy jest włączona (PATCH { legal_hold })."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        }
      },
      "MatterSeal": {
        "type": "object",
        "properties": {
          "root_hash": {
            "type": "string",
            "description": "sha256:<hex>: skrót ostatniego ogniwa łańcucha dowodowego, który został zapieczętowany i przesłany do OpenTimestamps."
          },
          "ots": {
            "type": "string",
            "description": "Dowód OpenTimestamps, hex."
          },
          "ots_pending": {
            "type": "boolean"
          },
          "eidas": {
            "type": "object",
            "properties": {
              "tsr_base64": {
                "type": "string",
                "nullable": true
              },
              "serial": {
                "type": "string",
                "nullable": true
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "ltv": {
                "type": "boolean"
              },
              "verification_status": {
                "type": "string",
                "enum": [
                  "verified"
                ],
                "description": "Token przeszedł kontrolę ProofSnap przy zapieczętowaniu (wspólny weryfikator, te same kontrole co dla tokenu przechwycenia)."
              },
              "revocation_status": {
                "type": "string",
                "enum": [
                  "good",
                  "revoked_after_gen_time",
                  "unknown",
                  "not_checked"
                ],
                "description": "Status certyfikatu TSA ustalony przy pieczętowaniu na podstawie zebranej wówczas odpowiedzi OCSP lub listy CRL."
              },
              "validation": {
                "type": "object",
                "nullable": true,
                "description": "Zawartość eidas_validation.json zapisana przy zapieczętowaniu, do sprawdzenia tokenu offline: tsaCertChain, rootCaPem, ocspResponse, crlResponse (pomijana i wymieniana w omitted, gdy jest zbyt duża), ocspUrl, ocspFetchedAt, ocspStatus, crlUrl, verification. Zapieczętowania wykonane przed 2026-10-01 jej nie mają.",
                "additionalProperties": true
              }
            },
            "nullable": true,
            "description": "Znacznik czasu eIDAS nad tym samym skrótem, gdy wdrożenie ma skonfigurowany eIDAS, wywołujący działa w środowisku produkcyjnym, a token został uzyskany i przeszedł kontrolę, w przeciwnym razie null (zobacz eidas_status)."
          },
          "sealed_by": {
            "type": "string",
            "nullable": true
          },
          "sealed_at": {
            "type": "string",
            "format": "date-time"
          },
          "eidas_status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained",
              "not_configured"
            ],
            "description": "Brak w pieczęciach utworzonych przed 2026-10-01. not_obtained: pieczęć zawiera tylko zakotwiczenie OpenTimestamps (eidas_reason token_refused, unavailable lub test_key). Przekroczenie czasu przy znaczniku eIDAS nigdy nie powoduje utworzenia pieczęci: żądanie zwraca wtedy 503 EIDAS_SEAL_RETRY."
          },
          "eidas_reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "test_key",
              "account_limit"
            ],
            "description": "Tylko dla not_obtained. test_key: zapieczętowane przez wywołującego w trybie testowym (testowy klucz API lub sesja sandbox), więc nie zażądano znacznika czasu eIDAS. account_limit: konto nie ma jeszcze żadnej opłaconej faktury i osiągnęło swój limit eIDAS (20 na dobę UTC, 50 na miesiąc UTC)."
          }
        },
        "nullable": true
      },
      "CustodyEntry": {
        "type": "object",
        "properties": {
          "seq": {
            "type": "integer"
          },
          "ts": {
            "type": "string",
            "format": "date-time"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "enum": [
              "api",
              "user"
            ],
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "Na przykład matter.created, matter.updated, capture.added, capture.removed, capture.legal_hold_placed, capture.legal_hold_released, member.added, member.removed, package.imported (pakiet dowodowy zaimportowany z matter_id; target to id przechwycenia)."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "prev_hash": {
            "type": "string"
          },
          "entry_hash": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "Tylko dla package.imported: manifest_sha256, zip_sha256, package_kind, verdict, levels. Przechowywane razem z wpisem, ale NIE objęte entry_hash.",
            "additionalProperties": true
          }
        },
        "description": "Jedno ogniwo łańcucha skrótów. entry_hash obejmuje seq, ts, actor, action, target i prev_hash."
      },
      "MatterCapture": {
        "type": "object",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "nullable": true
          },
          "source": {
            "type": "string"
          },
          "label": {
            "type": "string",
            "nullable": true
          },
          "created_at": {
            "type": "string"
          },
          "captured_at": {
            "type": "string",
            "nullable": true
          },
          "file_count": {
            "type": "integer",
            "nullable": true
          },
          "manifest_hash": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "AuditEntry": {
        "type": "object",
        "properties": {
          "ts": {
            "type": "string"
          },
          "actor_uid": {
            "type": "string",
            "nullable": true
          },
          "actor_name": {
            "type": "string",
            "nullable": true
          },
          "actor_role": {
            "type": "string",
            "nullable": true
          },
          "action": {
            "type": "string",
            "description": "Na przykład capture.downloaded, export.generated, share.created, share.revoked."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "source_ip": {
            "type": "string",
            "nullable": true,
            "description": "Tylko dla właściciela sprawy (u członków brak)."
          },
          "user_agent": {
            "type": "string",
            "nullable": true,
            "description": "Tylko dla właściciela sprawy (u członków brak)."
          }
        },
        "description": "Zdarzenie dostępu. Nie należy do łańcucha skrótów."
      },
      "CertifyResult": {
        "type": "object",
        "properties": {
          "certify_id": {
            "type": "string",
            "example": "cert_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "hash_sha256": {
            "type": "string",
            "description": "Tylko pojedynczy certyfikat."
          },
          "combined": {
            "type": "boolean",
            "description": "Tylko certyfikat zbiorczy."
          },
          "file_count": {
            "type": "integer",
            "description": "Tylko certyfikat zbiorczy."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Etykieta, którą wysłano.",
                  "nullable": true
                },
                "sha256": {
                  "type": "string"
                }
              }
            },
            "description": "Tylko certyfikat zbiorczy."
          },
          "label": {
            "type": "string",
            "description": "Twoja etykieta albo \"N files\" dla certyfikatu łączonego.",
            "nullable": true
          },
          "certified_at": {
            "type": "string",
            "format": "date-time"
          },
          "timestamps": {
            "type": "object",
            "properties": {
              "bitcoin_ots": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "pending_confirmation",
                      "not_present"
                    ],
                    "description": "pending_confirmation: manifest.json.ots jest w pakiecie i czeka na blok Bitcoina. not_present: kalendarze OpenTimestamps były nieosiągalne, pakiet nie zawiera manifest.json.ots, a manifest.json go nie wymienia."
                  },
                  "block_height": {
                    "type": "integer",
                    "nullable": true
                  }
                }
              },
              "eidas": {
                "$ref": "#/components/schemas/EidasTimestamp"
              }
            }
          },
          "package_url": {
            "type": "string",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "format": "date-time"
          },
          "verify_url": {
            "type": "string",
            "description": "Link do publicznego weryfikatora ProofSnap.",
            "format": "uri"
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "PlainText": {
        "type": "string",
        "description": "Reguła zwykłego tekstu dla krótkich pól tekstu dowolnego, które są drukowane na dokumentach przeznaczonych dla sądu lub na stronach publicznych (case_reference, case_id, case_note, wartości evidence_pack.custodian, case_reference w certify-hash, custodian zaczernienia, oświadczenie eksportu, nazwy firm i spraw). Wartość jest normalizowana do NFC, białe znaki są scalane i przycinane, a długość jest liczona w znakach (punktach kodowych Unicode). Odrzucane są: znaki sterujące (C0, C1), osadzenia, nadpisania i izolacje bidi (od U+202A do U+202E, od U+2066 do U+2069), znaczniki kierunku (U+200E, U+200F, U+061C), znaki o zerowej szerokości i znaki niewidoczne (U+200B, od U+2060 do U+2064, U+FEFF, U+00AD, U+034F, U+180E, od U+206A do U+206F, od U+FFF9 do U+FFFB), znaki wypełniające hangul (U+115F, U+1160, U+3164, U+FFA0), znaki tagów (od U+E0000 do U+E007F), osamotnione surogaty i znaki do użytku prywatnego. Przechodzą litery wszystkich pism, w tym sylaby hangul, pismo arabskie, hebrajskie, dewanagari i CJK, a U+200C / U+200D (ZWNJ, ZWJ) są dozwolone, ponieważ wymaga ich poprawna pisownia w kilku pismach."
      },
      "EidasTimestamp": {
        "type": "object",
        "description": "Wynik eIDAS, gdy dla produkcyjnego klucza API zażądano include_eidas; brak, gdy nie zażądano. confirmed: pakiet zawiera manifest.json.tsr (rozliczane, chyba że stan unieważnienia jest nieznany). not_obtained: żaden token nie został dostarczony i nic nie zostało rozliczone; pakiet zawiera tylko zakotwiczenie OpenTimestamps (reason token_refused: token został zwrócony, ale nie przeszedł kontroli przy wydaniu; unavailable: nie udało się uzyskać tokenu; time_budget: żądanie nie zmieściło się w pozostałym czasie, więc nie zostało wysłane, zostało odrzucone przez usługę znaczników czasu jeszcze przed żądaniem do urzędu albo zostało przerwane, co zdarza się na synchronicznej ścieżce zaczerniania lub pod koniec długiego przechwytywania; account_limit: konto nie ma jeszcze żadnej opłaconej faktury i osiągnęło swój limit eIDAS wynoszący 20 znaczników na dzień UTC lub 50 na miesiąc UTC, liczony łącznie dla przechwyceń, partii, automatycznych przechwyceń, certyfikacji, porównań, zaczernień i zapieczętowań spraw, więc żadne żądanie nie zostało wysłane; konta z opłaconą fakturą nie są ograniczone). Wiersze zakończone przed 2026-10-01 podają tylko confirmed.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "time_budget",
              "account_limit"
            ],
            "description": "Tylko dla not_obtained."
          },
          "serial": {
            "type": "string",
            "nullable": true,
            "description": "Tylko confirmed."
          },
          "issued_at": {
            "type": "string",
            "nullable": true,
            "description": "Tylko confirmed."
          },
          "revocation": {
            "type": "string",
            "enum": [
              "unknown"
            ],
            "description": "Tylko confirmed, i to tylko wtedy, gdy nie udało się ustalić statusu certyfikatu TSA przy wydaniu (token przeszedł wszystkie pozostałe kontrole). Token trafia do pakietu i nie jest rozliczany."
          },
          "billed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Występuje (false) razem z revocation unknown: ten token nie został rozliczony."
          }
        }
      },
      "ExternalArchive": {
        "type": "object",
        "description": "Wynik przechwycenia w Internet Archive (Wayback Machine). Internet Archive to publiczne archiwum prowadzone przez podmiot trzeci.",
        "required": [
          "requested"
        ],
        "properties": {
          "requested": {
            "type": "boolean",
            "description": "false, gdy nie wysłano żadnego żądania do Internet Archive (external_archive: false, certyfikacja lub porównanie, przechwycenie wykonane, zanim istniała archiwizacja, albo żądanie nie zmieściło się w budżecie czasu). Wszystkie pozostałe pola są wtedy nieobecne."
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "Bieżący wynik. Dla zadania Save Page Now, które w chwili pieczętowania wciąż oczekuje, planista odczytuje status zadania około 5 i 30 minut po żądaniu, a ta wartość staje się wynikiem końcowym (created, recent_existing, failed, rate_limited lub unconfirmed, gdy w oknie sprawdzania nie nadeszła odpowiedź). created, recent_existing, created_after_request i existing zawierają snapshot_url."
          },
          "status_at_sealing": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "Stan zapieczętowany w pakiecie (zapisują go metadata.json, forensic_log.json, chain_of_custody.json i evidence.pdf). Sam pakiet nigdy się nie zmienia; jest równy status, chyba że kontrola uzupełniająca rozstrzygnęła oczekujące zadanie."
          },
          "method": {
            "type": "string",
            "nullable": true,
            "enum": [
              "spn2_authenticated",
              "spn_anonymous",
              null
            ]
          },
          "requested_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "snapshot_url": {
            "type": "string",
            "format": "uri",
            "nullable": true,
            "description": "Publiczna kopia w Wayback Machine, zawsze https://web.archive.org/web/{snapshot_timestamp}/{url}."
          },
          "snapshot_timestamp": {
            "type": "string",
            "nullable": true,
            "description": "Znacznik czasu Wayback YYYYMMDDhhmmss (UTC)."
          },
          "snapshot_offset_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "Czas migawki minus czas przechwycenia, w sekundach (wartość ujemna: przed przechwyceniem)."
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Kiedy kontrola uzupełniająca planisty zapisała wynik końcowy; null, gdy zapieczętowany stan był końcowy."
          },
          "job_id": {
            "type": "string",
            "nullable": true,
            "description": "Identyfikator zadania Save Page Now (uwierzytelnione żądania)."
          },
          "follow_up_pending": {
            "type": "boolean",
            "description": "true, dopóki planista musi jeszcze odczytać status zadania."
          },
          "skip_reason": {
            "type": "string",
            "nullable": true,
            "description": "Dlaczego adres URL nie został wysłany, gdy status ma wartość skipped, skipped_sensitive lub skipped_query: credentials_in_url, sensitive_path, opaque_path_segment, sensitive_fragment, sensitive_parameter_name, opaque_parameter_value, query_string (w starszych wierszach sam status)."
          },
          "resolved": {
            "type": "object",
            "nullable": true,
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "created",
                  "recent_existing",
                  "failed",
                  "rate_limited",
                  "unresolved"
                ]
              },
              "job_status": {
                "type": "string",
                "nullable": true,
                "description": "success, error lub pending według zadania Save Page Now, null, gdy nie udało się go odczytać."
              },
              "snapshot_url": {
                "type": "string",
                "nullable": true,
                "description": "https://web.archive.org/web/<14 digits>/<URL>, null, gdy brak lub jest nieprawidłowy."
              },
              "snapshot_timestamp": {
                "type": "string",
                "nullable": true
              },
              "checked_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "reason": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "no_request_time",
                  "capture_deleted",
                  "capture_failed",
                  null
                ],
                "description": "Dlaczego brak odpowiedzi zadania: no_request_time (wiersz nie ma czasu żądania, od którego można zaplanować odczyty), capture_deleted lub capture_failed (przechwycenie zostało przeniesione do kosza lub zakończyło się niepowodzeniem, gdy zadanie oczekiwało, więc nie przeprowadzono kontroli uzupełniającej; dla przechwycenia przywróconego z kosza w ciągu 30 minut od żądania kontrola uzupełniająca jest przeprowadzana ponownie). W pozostałych przypadkach null."
              }
            },
            "description": "Zapis kontroli uzupełniającej (historia) jako stała projekcja: snapshot_url tylko wtedy, gdy jest to prawidłowy adres URL migawki Wayback dla snapshot_timestamp; reason tylko wtedy, gdy kontrola uzupełniająca zakończyła się bez odpowiedzi zadania."
          }
        }
      },
      "CaptureStorage": {
        "type": "object",
        "description": "Okres przechowywania, kopia zapasowa i (w przypadku automatycznych przechwyceń) historia wersji.",
        "properties": {
          "retention": {
            "type": "object",
            "properties": {
              "years": {
                "type": "integer",
                "example": 10
              },
              "counted_from": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Późniejsza z wartości created_at i ostatniego znanego użycia: ostatnia kontrola, która stwierdziła, że strona się nie zmieniła, albo chwila, w której to przechwycenie zostało zastąpione nowszym przechwyceniem tego samego automatycznego przechwycenia."
              },
              "earliest_deletion_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "counted_from plus liczba lat, w przybliżeniu: usunięcie następuje przy którymś z kolejnych przebiegów planisty, a późniejsze kontrole mogą je przesunąć. null przy blokadzie prawnej."
              },
              "legal_hold": {
                "type": "boolean"
              }
            }
          },
          "duplicate_copy": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "pending",
                  "stored",
                  "not_copied"
                ],
                "description": "stored: druga kopia pakietu (wraz z jego uaktualnionym dowodem OTS) jest przechowywana w osobnym buckecie w innym regionie UE i usuwana razem z oryginałem. pending: trwa kopiowanie (kilka minut). none: pakiety testowe oraz pakiety sprzed wprowadzenia duplikatu, dopóki nie zostaną skopiowane."
              },
              "copied_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "version": {
            "type": "object",
            "description": "Tylko automatyczne przechwycenia.",
            "properties": {
              "superseded_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "superseded_by": {
                "type": "string",
                "nullable": true
              },
              "unchanged_first_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Pierwsza kontrola (on_change), która stwierdziła, że tekst strony nie zmienił się od tego przechwycenia."
              },
              "unchanged_last_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "unchanged_checks": {
                "type": "integer",
                "description": "Zarejestrowane kontrole bez zmian (najwyżej jedna na godzinę)."
              }
            }
          }
        }
      },
      "WebhookEventType": {
        "type": "string",
        "enum": [
          "capture.completed",
          "capture.failed",
          "certify.completed",
          "certify.failed",
          "redaction.completed",
          "redaction.failed",
          "monitor.change_detected",
          "capture.anchor_confirmed",
          "schedule.paused"
        ],
        "description": "Zdarzenie z katalogu (tag Webhooks)."
      },
      "WebhookSubscriptionFilters": {
        "type": "object",
        "description": "Najwyżej jedno z host i schedule_id. Subskrypcja otrzymuje wtedy tylko zdarzenia wierszy z tym hostem (nazwa hosta przechwyconego URL małymi literami) lub z tego automatycznego przechwycenia. `{}` oznacza brak filtrów.",
        "properties": {
          "host": {
            "type": "string",
            "example": "www.example.com"
          },
          "schedule_id": {
            "type": "string",
            "example": "sch_a1B2c3D4e5F6g7H8"
          }
        }
      },
      "WebhookSubscription": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "Endpoint https, do którego zdarzenia są wysyłane metodą POST."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Środowisko poświadczenia, którym została utworzona. Subskrypcja produkcyjna otrzymuje tylko zdarzenia wierszy produkcyjnych, a testowa tylko zdarzenia wierszy testowych."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: utworzona kluczem API; unieważnienie tego klucza wyłącza subskrypcję (disabled_reason key_revoked)."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Nieudane dostarczenia od ostatniego udanego."
          },
          "last_success_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_failure_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "last_error": {
            "type": "string",
            "nullable": true,
            "description": "Klasa błędu ostatniego nieudanego dostarczenia (timeout, connection_failed, blocked_address, redirect_not_followed, http_<status>)."
          },
          "disabled_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "gone",
              "failing",
              "key_revoked",
              null
            ],
            "description": "gone: endpoint odpowiedział kodem 410. failing: wszystkie dostarczenia kończyły się niepowodzeniem przez 5 dni bez ani jednego sukcesu. key_revoked: klucz API, którym została utworzona, został unieważniony. PATCH ze status active aktywuje ją ponownie."
          },
          "disabled_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "consecutive_failures",
          "last_success_at",
          "last_failure_at",
          "last_error",
          "disabled_reason",
          "disabled_at"
        ]
      },
      "WebhookSubscriptionCreated": {
        "type": "object",
        "properties": {
          "subscription_id": {
            "type": "string",
            "example": "whs_a1B2c3D4e5F6g7H8"
          },
          "url": {
            "type": "string",
            "description": "Endpoint https, do którego zdarzenia są wysyłane metodą POST."
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/WebhookSubscriptionFilters"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ],
            "description": "Środowisko poświadczenia, którym została utworzona. Subskrypcja produkcyjna otrzymuje tylko zdarzenia wierszy produkcyjnych, a testowa tylko zdarzenia wierszy testowych."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: utworzona kluczem API; unieważnienie tego klucza wyłącza subskrypcję (disabled_reason key_revoked)."
          },
          "secret": {
            "type": "string",
            "description": "whsec_, po którym następuje standardowy base64. Wyświetlany JEDEN RAZ: każde dostarczenie do tej subskrypcji jest nim podpisane (Standard Webhooks). Zapisz go teraz; nie można go później odczytać. Powtórzenie żądania z tym samym Idempotency-Key w ciągu 24 godzin zwraca tę samą treść, łącznie z sekretem."
          }
        },
        "required": [
          "subscription_id",
          "url",
          "events",
          "filters",
          "description",
          "status",
          "env",
          "created_at",
          "created_by",
          "secret"
        ]
      },
      "Event": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "example": "evt_a1B2c3D4e5F6g7H8"
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "env": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "data": {
            "type": "object",
            "description": "Treść zdarzenia w postaci, w jakiej otrzymują ją subskrypcje, bez subscription_id i bez linków wstępnie podpisanych (package_url, package_url_expires_at, upgraded_proof_url, compare.package_url)."
          }
        },
        "required": [
          "id",
          "type",
          "created_at",
          "env",
          "data"
        ]
      },
      "PrivateNote": {
        "type": "string",
        "nullable": true,
        "maxLength": 200,
        "description": "Twoja prywatna notatka do przechwycenia (na przykład \"przechwycenie z telefonu\"), najwyżej 200 znaków, zwykły tekst (reguła PlainText). Jest zapisywana tylko w rekordzie przechwycenia: NIE jest częścią zapieczętowanego pakietu dowodowego (nie ma jej w ZIP, PDF, manifeście ani w żadnym skrócie) i nie ma żadnej wartości dowodowej, dlatego można ją później zmienić lub wyczyścić przez PATCH /v1/captures/{capture_id} bez wpływu na dowody. Odczytuje ją tylko autor przechwycenia. Jeśli nie chcesz notatki, pomiń pole albo wyślij null lub pusty ciąg.",
        "example": "mobile capture"
      },
      "CaptureEvent": {
        "type": "object",
        "description": "Jedno zdarzenie z łańcucha zdarzeń przechwyceń konta. hash = SHA-256 (hex) kanonicznego JSON pól objętych skrótem (v, chain, seq, type, capture_id, manifest_sha256, actor, access, detail, at, prev_hash; klucze posortowane, bez białych znaków, brakujące wartości jako null). prev_hash to skrót poprzedniego zdarzenia, dla seq 1 jest to 64 zer.",
        "properties": {
          "v": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "chain": {
            "type": "string",
            "description": "Konto: uid dla przechwyceń osobistych, org_<org_id> dla przechwyceń firmy."
          },
          "seq": {
            "type": "integer",
            "minimum": 1
          },
          "type": {
            "type": "string",
            "enum": [
              "trash",
              "restore",
              "erase",
              "purge",
              "retention_deletion",
              "account_deletion",
              "company_deletion",
              "legal_hold_on",
              "legal_hold_off",
              "s3_legal_hold",
              "import"
            ]
          },
          "capture_id": {
            "type": "string"
          },
          "manifest_sha256": {
            "type": "string",
            "nullable": true,
            "description": "SHA-256 pliku manifest.json przechwycenia, odczytany przed jakimkolwiek usunięciem."
          },
          "actor": {
            "type": "string",
            "description": "uid działającego konta albo system dla przebiegów porządkowych serwera."
          },
          "access": {
            "type": "string",
            "description": "author, company_owner, matter_owner lub system."
          },
          "detail": {
            "type": "string",
            "nullable": true,
            "description": "legal_hold_on / legal_hold_off: źródło blokady (manual, bulk, matter:<id>, derived:<capture_id>) albo, przy zdjęciu przez system (actor system), kod powodu subscription_ended; s3_legal_hold: wynik w S3 (on, off, failed, unsupported, none); import: rodzaj zaimportowanego pakietu. Bez dowolnego tekstu."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "Kiedy zdarzenie zostało zapisane w łańcuchu. Nigdy wcześniej niż at poprzedniego zdarzenia."
          },
          "prev_hash": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "Kiedy sama akcja została zatwierdzona (transakcja zmiany stanu). Nie jest objęte skrótem. Zwykle różni się od at tylko o milisekundy; później tylko wtedy, gdy serwer ponowił dopisanie do łańcucha (akcja jest zapisywana w tej samej transakcji co zmiana, więc żadna nie ginie)."
          }
        }
      },
      "CaptureEventAnchor": {
        "type": "object",
        "description": "Codzienne zakotwiczenie łańcucha w OpenTimestamps: head_hash to skrót zdarzenia to_seq, który obejmuje wszystkie wcześniejsze zdarzenia.",
        "properties": {
          "anchor_id": {
            "type": "string"
          },
          "day": {
            "type": "string",
            "format": "date"
          },
          "from_seq": {
            "type": "integer"
          },
          "to_seq": {
            "type": "integer"
          },
          "head_hash": {
            "type": "string"
          },
          "ots_proof_base64": {
            "type": "string",
            "nullable": true,
            "description": "Dowód OpenTimestamps (plik .ots, base64) dla head_hash."
          },
          "ots_status": {
            "type": "string",
            "enum": [
              "pending",
              "bitcoin",
              "unconfirmed"
            ],
            "description": "pending: ots_proof_base64 zawiera zobowiązanie kalendarzy (ots verify odpytuje kalendarze); bitcoin: zostało zastąpione dowodem uaktualnionym do bloku Bitcoina (bitcoin_block_height); unconfirmed: w ciągu 14 dni nie zwrócono dowodu w Bitcoinie, przechowywany dowód nadal można zweryfikować przez kalendarze."
          },
          "bitcoin_block_height": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ImportSummary": {
        "type": "object",
        "description": "Trzy poziomy zweryfikowanego pakietu.",
        "properties": {
          "kind_label_key": {
            "type": "string",
            "description": "Klucz etykiety rodzaju pakietu, import_kind_<kind>."
          },
          "integrity": {
            "type": "string",
            "enum": [
              "verified",
              "failed"
            ]
          },
          "time_anchor": {
            "type": "string",
            "enum": [
              "eidas_qualified",
              "eidas_qualified_revocation_unknown",
              "ots_bitcoin_attested_unconfirmed",
              "ots_pending",
              "none"
            ]
          },
          "provenance": {
            "type": "string",
            "nullable": true,
            "enum": [
              "issued_by_proofsnap",
              "unverified",
              "registered_by_extension_account",
              "not_registered",
              "unknown",
              null
            ]
          },
          "registered_at": {
            "type": "string",
            "format": "date-time",
            "description": "Kiedy rejestr rozszerzenia zapisał pakiet (provenance registered_by_extension_account)."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "Kiedy rejestr API zapisał wydanie (provenance issued_by_proofsnap)."
          }
        }
      },
      "Import": {
        "type": "object",
        "properties": {
          "import_id": {
            "type": "string",
            "example": "imp_a1B2c3D4e5F6g7H8i9J0"
          },
          "status": {
            "type": "string",
            "enum": [
              "uploading",
              "verifying",
              "accepted",
              "rejected",
              "duplicate",
              "expired"
            ]
          },
          "filename": {
            "type": "string"
          },
          "size_bytes": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "upload_expires_at": {
            "type": "string",
            "format": "date-time",
            "description": "Tylko dla uploading: zakończ przed tym czasem (24 godziny od utworzenia)."
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "description": "Kiedy import osiągnął status końcowy."
          },
          "capture_id": {
            "type": "string",
            "description": "Tylko accepted: nowe przechwycenie (ten sam identyfikator co import)."
          },
          "duplicate_of": {
            "type": "string",
            "description": "Tylko duplicate: Twoje przechwycenie, które już zawiera ten manifest."
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ],
            "description": "Rodzaj pakietu, gdy zostanie rozpoznany."
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "rejected i expired: kody powodów, na przykład file_mismatch, file_missing, unlisted_file, bad_signature, no_hashed_files, unknown_schema, zip_too_large, test_package, ots_digest_mismatch, eidas_token_failed, forensic_chain_broken, size_mismatch, upload_not_completed, verification_incomplete. Tylko kody, nigdy nazwy plików."
          },
          "zip_sha256": {
            "type": "string",
            "description": "SHA-256 przesłanego pliku ZIP, gdy zostanie obliczony."
          },
          "manifest_sha256": {
            "type": "string",
            "description": "SHA-256 pliku manifest.json pakietu (accepted i duplicate)."
          },
          "matter_id": {
            "type": "string",
            "description": "Sprawa wskazana przy tworzeniu importu."
          },
          "matter_assignment": {
            "type": "string",
            "enum": [
              "assigned",
              "skipped"
            ],
            "description": "accepted z matter_id: czy przechwycenie zostało dodane do sprawy (skipped, gdy sprawa została do tego czasu zapieczętowana, zarchiwizowana lub nie była już dla Ciebie otwarta; import pozostaje accepted)."
          },
          "upload": {
            "type": "object",
            "description": "Tylko z ?upload=true, dopóki status ma wartość uploading.",
            "properties": {
              "upload_id": {
                "type": "string"
              },
              "part_size": {
                "type": "integer"
              },
              "parts": {
                "type": "array",
                "description": "Wstępnie podpisane adresy PUT części, które nie zostały jeszcze odebrane; wyślij przez PUT dokładnie size_bytes.",
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "url": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "expires_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "received": {
                "type": "array",
                "nullable": true,
                "items": {
                  "type": "object",
                  "properties": {
                    "part_number": {
                      "type": "integer"
                    },
                    "etag": {
                      "type": "string"
                    },
                    "size_bytes": {
                      "type": "integer"
                    }
                  }
                }
              },
              "completed": {
                "type": "boolean",
                "description": "Przesyłanie zostało już zakończone: wyślij complete ponownie."
              }
            }
          }
        }
      },
      "ImportVerification": {
        "type": "object",
        "description": "Raport z weryfikacji zapisany przy imporcie (bez zawartości plików, bez szczegółów w postaci dowolnego tekstu).",
        "properties": {
          "ok": {
            "type": "boolean"
          },
          "kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "format": {
            "type": "string"
          },
          "kind_label_key": {
            "type": "string"
          },
          "evidence_id": {
            "type": "string",
            "description": "Identyfikator, który nosi pakiet (ps_..., FC-..., cap_..., cert_..., cmp_..., red_...)."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "anchor_sha256": {
            "type": "string",
            "description": "Skrót, którego dotyczą znaczniki czasu: skrót manifestu, a przy certyfikacji pojedynczego pliku skrót certyfikowanego pliku."
          },
          "anchor_source": {
            "type": "string",
            "enum": [
              "manifest",
              "certified_file"
            ]
          },
          "checks": {
            "type": "object",
            "description": "structure, signature, files (listed, verified, presence_only, unhashed, os_junk_ignored), custody, forensic_chain, ots, eidas, test_mode.",
            "additionalProperties": true
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CaptureImport": {
        "type": "object",
        "description": "Tylko w przechwyceniu z source \"import\".",
        "properties": {
          "import_id": {
            "type": "string"
          },
          "package_kind": {
            "type": "string",
            "enum": [
              "web_capture_v21",
              "web_capture_legacy",
              "session",
              "file_certification_single",
              "file_certification_combined",
              "api_capture",
              "api_certify",
              "api_compare",
              "api_redaction"
            ]
          },
          "kind_label_key": {
            "type": "string"
          },
          "package_format": {
            "type": "string"
          },
          "original_evidence_id": {
            "type": "string"
          },
          "original_filename": {
            "type": "string",
            "nullable": true
          },
          "imported_at": {
            "type": "string",
            "format": "date-time"
          },
          "captured_at_basis": {
            "type": "string",
            "enum": [
              "package_claim"
            ],
            "description": "captured_at importowanego pakietu to czas podany w pakiecie, nigdy czas zaobserwowany przez ProofSnap."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "levels": {
            "type": "object",
            "properties": {
              "integrity": {
                "type": "string"
              },
              "time_anchor": {
                "type": "string"
              },
              "provenance": {
                "type": "string",
                "nullable": true
              }
            }
          },
          "verification": {
            "$ref": "#/components/schemas/ImportVerification"
          },
          "provenance": {
            "type": "object",
            "properties": {
              "level": {
                "type": "string"
              },
              "source": {
                "type": "string",
                "nullable": true,
                "enum": [
                  "api_ledger",
                  "extension_registry",
                  null
                ]
              },
              "issued_at": {
                "type": "string",
                "nullable": true
              },
              "registered_at": {
                "type": "string",
                "nullable": true
              },
              "reason": {
                "type": "string"
              }
            }
          }
        }
      },
      "Rule902DisabledWarnings": {
        "type": "array",
        "description": "Występuje tylko wtedy, gdy żądanie prosiło o szablon certyfikacji (include_rule_902: true lub, w przypadku przechwycenia, niepuste evidence_pack.custodian), a szablon jest wyłączony. Żądanie zostało przyjęte, a flaga zignorowana: rule_902_certification.pdf nie jest generowany.",
        "items": {
          "type": "object",
          "properties": {
            "code": {
              "type": "string",
              "enum": [
                "RULE902_DISABLED"
              ]
            },
            "message": {
              "type": "string",
              "example": "The FRE 902 certification template (rule_902_certification.pdf) is currently disabled. include_rule_902 was accepted and ignored: the package does not contain the template."
            }
          },
          "required": [
            "code",
            "message"
          ]
        }
      }
    }
  }
}
