{
  "openapi": "3.0.3",
  "info": {
    "title": "ProofSnap API",
    "version": "2.0.0",
    "license": {
      "name": "Proprietary",
      "url": "https://getproofsnap.com/terms.html"
    },
    "description": "ProofSnap API загружает веб-страницу в серверном браузере Chromium и возвращает пакет доказательств (ZIP).\n\nКаждый пакет снимка содержит скриншот, HTML страницы, извлечённый текст страницы, metadata.json, forensic_log.json, chain_of_custody.json, evidence.pdf и manifest.json, в котором указан SHA-256 каждого файла. manifest.json подписывается ключом RSA-4096, созданным для этого снимка (открытый ключ поставляется как publickey.pem, закрытый ключ уничтожается), а SHA-256 файла manifest.json отправляется в календари OpenTimestamps для привязки к Bitcoin (manifest.json.ots). Если запрос к календарю не удался, пакет поставляется без доказательства и manifest.json его не указывает, поэтому офлайн-скрипты и верификатор сообщают, что файла нет в пакете, а не что он утерян. При `include_eidas` с рабочим ключом тот же хеш также получает квалифицированную метку времени eIDAS (RFC 3161, manifest.json.tsr). JPEG-скриншоты подписываются C2PA Content Credentials по возможности: если подписать не удалось, снимок завершается без них. Папка verification/ содержит скрипты для офлайн-проверки пакета. Каждый manifest.json содержит `ots_listed_only_when_present: true` (manifest.json.ots указывается, только если он есть в пакете) и `eidas_marker_version: 1`. Значение `eidas_requested: true` записывается, только если пакет поставляется с квалифицированной меткой времени. Если токен получить не удалось, манифест опечатывается повторно без него. Поэтому пакет, в котором записано `eidas_requested: true`, но нет manifest.json.tsr, потерял свой токен, и офлайн-скрипты сообщают FAIL. chain_of_custody.json фиксирует `bitcoin_ots_included` и перечисляет в `page_modifications.items[]` все изменения, внесённые в страницу перед снимком. manifest.json содержит sealed_at (момент его сборки, после хеширования всех файлов) и смещения часов сервера снимков, измеренные по SNTP; chain_of_custody.json перечисляет этапы снимка в порядке выполнения, каждый со временем завершения, и заканчивается этапом custody_record_written (хеширование, подпись и метки времени выполняются после него и подтверждаются файлом manifest.json и доказательствами).\n\nmetadata.json фиксирует:\n\n- `browser_identity`: фактически отправленные user agent и Client Hints.\n- `dns`: независимую DNS-проверку адреса, к которому подключился браузер, с ответами dns.google и cloudflare-dns.com и итогом: confirmed, not_confirmed, resolvers_unavailable, connected_ip_unknown, skipped_ip_literal (URL указывает IP-адрес, поэтому нет имени хоста для поиска) или skipped_internal_host (внутреннее имя хоста, которое никогда не отправляется публичным DNS-серверам).\n- `url_at_load` и `url_at_screenshot`: адрес сразу после загрузки страницы и в момент создания её изображения (страница может менять собственный адрес, чаще всего часть после #).\n- `redirect_chain`: перенаправления основного документа, от самого раннего, каждое со своим HTTP-статусом. Запрошенный и конечный адреса приводятся полностью; в каждом промежуточном переходе значения параметров запроса заменены на [redacted] (`query_values_redacted`).\n- `tls.source`: источник каждого поля TLS, `browser_connection` или `browser_connection_and_separate_handshake`, во втором случае с `at`, `ip` и `port` отдельного рукопожатия, которое получило certificate_chain.pem после загрузки страницы.\n\nЕсли запрошенный этап не был выполнен (метка времени eIDAS, подпись C2PA, запрос в архив, WHOIS, второе считывание часов, WARC, обрезанный из-за ограничения времени, или ожидание либо раскрытие содержимого, пропущенное из-за ограничения времени), evidence.pdf указывает его с кратким объяснением причины, а сертификат происхождения называет его в одной строке.\n\nСнимки выполняются асинхронно. POST /captures возвращает 202 с capture_id; опрашивайте GET /captures/{capture_id} или передайте `options.webhook_url`. Завершённый снимок возвращает предподписанную ссылку package_url, действующую до 24 часов (см. package_url_expires_at); чтобы получить новую ссылку, повторно запросите данные снимка.\n\nПути ниже указаны относительно URL сервера, который уже содержит этап /v1. Значения `poll_path`, возвращаемые API, включают /v1.",
    "contact": {
      "email": "support@getproofsnap.com"
    }
  },
  "servers": [
    {
      "url": "https://api.getproofsnap.com/v1",
      "description": "ProofSnap API (EU)"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Снимки",
      "description": "Создание, просмотр списка, чтение, удаление и проверка снимков.\n\n**Ограничения частоты запросов.** `POST /captures`, `POST /captures/batch`, `POST /compare`, `POST /captures/{capture_id}/redact` и `POST /certify-hash` ограничиваются на аккаунт в фиксированных 60-секундных окнах: все ключи API аккаунта и его сеанс в панели используют одно окно (добавление ключей не повышает лимит). Пакетный запрос (batch) или заверение нескольких хешей расходует одну единицу на каждый URL или хеш. Лимиты по умолчанию на окно по тарифам:\n\n| Тариф | Запросов за 60 с |\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| любой другой тариф | 30 |\n\nИз того же окна расходуют единицы и следующие запросы, по одной единице на запрос, если не указано иное: `POST /captures/{capture_id}/verify`, `PATCH /captures/{capture_id}` с action legal_hold, `GET /captures/hosts` (только когда список пересчитывается, а не когда возвращается кешированный результат), `POST /legal-holds` (одна единица на 10 снимков), `POST /captures/export-jobs`, а также `POST /imports`, `POST /imports/{import_id}/complete` и `GET /imports/{import_id}?upload=true`.\n\nПри превышении лимита API возвращает 429 RATE_LIMITED с заголовками `Retry-After`, `X-RateLimit-Limit`, `X-RateLimit-Remaining` и `X-RateLimit-Reset` и полем `error.details.retry_after_seconds`. Запрос, которому нужно больше единиц, чем допускает одно окно (заверение нескольких хешей сверх лимита на окно), никогда не будет выполнен и возвращает 400 RATE_LIMIT_COST_TOO_HIGH с `error.details` `{ max_per_window, window_seconds, requested }`. Разделите его на запросы меньшего размера. Отклонённый запрос не расходует единицы, а заголовки показывают фактический оставшийся запас.\n\n**Месячные лимиты.** Снимки, сравнения, зачернения и заверения на рабочих ключах учитываются в месячном максимуме аккаунта (календарный месяц по UTC). Принятый ответ содержит заголовки `X-Quota-*`. При достижении максимума возвращается 402 QUOTA_EXCEEDED с `error.details` `{ reason, used, limit, max_limit, cap, org_member }`, где `cap` равно `plan_max`, `customer`, `member_owner` или `org`. Пока у рабочей подписки просроченный платёж или она не оплачена, оплачиваемые запросы возвращают 402 PAYMENT_REQUIRED; для такого аккаунта снимки (одиночные и пакетные) отклоняются с этой ошибкой независимо от окружения ключа, в том числе для тестовых ключей.\n\n**Выполняемые снимки и тестовый режим.** Каждый аккаунт может одновременно выполнять только ограниченное число снимков (developer 10, startup 25, growth 100, legal_solo и legal_seats 25, custom 200, website_archive 25, sandbox 5). Сверх этого лимита API возвращает 429 CONCURRENT_CAPTURE_LIMIT; пакетный запрос запускает те URL, которые укладываются в лимит, а остальные перечисляет в `not_admitted` (у каждого `code` CONCURRENT_CAPTURE_LIMIT и `in_flight_limit`), поэтому отправьте их повторно позже. Работа в тестовом режиме (тестовый ключ или сеанс без привязанной оплаты) ограничена 200 единицами на аккаунт за сутки по UTC (429 TEST_MODE_DAILY_LIMIT; заголовки `X-Test-Daily-*`), а снимок в тестовом режиме никогда не отправляется в Internet Archive (`external_archive.skip_reason` \"test_mode\")."
    },
    {
      "name": "Сравнение и зачернение",
      "description": "Создать новый опечатанный пакет из существующих снимков: сравнение двух снимков или копию одного снимка с зачернёнными данными."
    },
    {
      "name": "Заверение хеша",
      "description": "Заверьте хеши SHA-256 файлов, которые хранятся у вас. Отправляются только хеши; сами файлы в ProofSnap не попадают."
    },
    {
      "name": "Снимки по расписанию",
      "description": "Периодические снимки (interval или cron) и мониторинг изменений (on_change). Каждый запуск, при котором делается снимок, является обычным снимком: он тарифицируется и учитывается в месячных лимитах."
    },
    {
      "name": "Дела",
      "description": "Рабочие пространства юридических дел с журналом цепочки хранения доказательств, связанным хешами. Доступны на тарифах Legal (legal_solo, legal_seats); на других тарифах возвращается 403 MATTERS_NOT_ENABLED, за двумя исключениями. Владелец компании, подписка которой закончилась, может по-прежнему просматривать список дел компании, открывать и экспортировать их до даты удаления данных компании (data_delete_after); такой доступ ограничен чтением и экспортом, и GET /matters в этом случае добавляет read_only: true. Аккаунт на тарифе Archive only может просматривать список своих дел, открывать и экспортировать их. Переименовать дело, перенести его в архив, управлять участниками, опечатать его, поделиться ссылкой на него или экспортировать его может только владелец дела. Владелец дела является его создателем. В деле компании владелец компании становится владельцем дела только после того, как создатель покинул компанию. Владелец компании, добавленный как участник, имеет только права участника. Участники получают 403 FORBIDDEN для этих операций. Для любой записи в деле (создание, PATCH, назначение снимков, публичная ссылка и её отзыв, опечатывание, экспорт) нужен подтверждённый адрес электронной почты (403 EMAIL_NOT_VERIFIED для сеанса в панели и для ключа API аккаунта, адрес которого не подтверждён), а для вызывающего в рабочем режиме (рабочий ключ API или сеанс с привязанной оплатой) запрос отклоняется с 402 PAYMENT_REQUIRED, пока по подписке (или по подписке компании) есть просроченный платёж или она не оплачена. Дело в архиве отклоняет переименование, новых участников, новые снимки, публичные ссылки и экспорт (409 MATTER_ARCHIVED), пока его не откроют снова; вызывающий, который не может открыть его снова (владелец компании, подписка которой закончилась, аккаунт Archive only), всё же может его экспортировать."
    },
    {
      "name": "Вебхуки",
      "description": "ProofSnap отправляет POST-запрос с событием в формате JSON на `webhook_url`, который вы передаёте вместе со снимком, заверением, зачернением или расписанием. URL должен использовать https, не должен содержать учётные данные (`user:password@`, 400 INVALID_URL) и не должен указывать на частный адрес, loopback, link-local, carrier-grade NAT, multicast, зарезервированный адрес или адрес облачных метаданных, включая формы IPv6, в которые встроен такой адрес IPv4. Это проверяется при приёме запроса (422 SSRF_BLOCKED) и повторно при каждой доставке: имя хоста разрешается, каждый адрес проверяется, и доставка подключается только к адресу, прошедшему проверку, поэтому к хосту, который позже начинает разрешаться в заблокированный адрес, подключение не выполняется. Если имя хоста не удаётся разрешить, доставка не отправляется. На всех маршрутах пустой или состоящий только из пробелов webhook_url считается отсутствием вебхука. /compare не отправляет вебхуки.\n\n**Подписки аккаунта.** Вместо webhook_url в каждом запросе (или вместе с ним) подпишите эндпоинт на события аккаунта через `POST /webhook-subscriptions`: на него поступает каждое подходящее событие аккаунта, независимо от того, какой ключ, расписание или сеанс в панели его вызвал. У каждой подписки свой секрет (возвращается один раз при создании), она перечисляет нужные типы событий и может фильтровать по `host` или `schedule_id`; рабочая (live) подписка получает события только рабочих снимков, а тестовая (test) подписка получает события только тестовых снимков. Ключ API видит и изменяет только подписки своего окружения; сеанс в панели видит все. Доставки по подписке содержат `subscription_id` на верхнем уровне и не содержат предподписанных ссылок (package_url, package_url_expires_at, upgraded_proof_url и compare.package_url удаляются; monitor.change_detected содержит compare.compare_id): получите пакет через GET /captures/{capture_id}. События снимков также содержат `capture_id` на верхнем уровне. webhook-id доставки по подписке выводится из подписки, снимка (или расписания) и события. Правила доставки и повторные попытки, описанные ниже, действуют и здесь. Кроме того, ответ 410 сразу отключает подписку (disabled_reason gone), доставки, которые завершаются ошибкой в течение 5 дней без единого успеха (при этом прошёл как минимум один полный цикл повторных попыток), отключают её (failing), а отзыв ключа API, создавшего подписку, отключает её (key_revoked); PATCH со status \"active\" снова её включает. Каждое событие также хранится 30 дней в `GET /events`.\n\n**Заголовки**\n\nКаждая доставка подписывается по спецификации [Standard Webhooks](https://www.standardwebhooks.com/), поэтому её может проверить любая официальная библиотека Standard Webhooks:\n\n| Заголовок | Значение |\n| --- | --- |\n| `Content-Type` | `application/json` |\n| `webhook-id` | `msg_<delivery id>`: одинаков для одного и того же события одного снимка, поэтому повторная доставка содержит тот же идентификатор. Используйте его для дедупликации: обрабатывайте каждый webhook-id один раз. `GET /webhook-events/{capture_id}` возвращает тот же идентификатор без префикса `msg_` как `delivery_id` |\n| `webhook-timestamp` | Время Unix в секундах, когда доставка была отправлена. Входит в подписываемые данные |\n| `webhook-signature` | `v1,<base64>`: HMAC-SHA256 от `<webhook-id>.<webhook-timestamp>.<raw body>`. Во время ротации секрета вебхука содержит две записи через пробел, `v1,<new> v1,<previous>`; принимайте доставку, если совпадает любая из них |\n| `User-Agent` | `ProofSnap-Webhook/2.0` |\n\n**Проверка доставки.** Проверяйте подпись по точным полученным байтам, до разбора. Отклоняйте доставку, у которой webhook-timestamp отличается от ваших часов более чем на 5 минут (официальные библиотеки делают это сами), и выполняйте дедупликацию по webhook-id. Ключ подписи: часть секрета после `whsec_`, декодированная из base64. `webhook_secret` состоит из `whsec_` и стандартного base64, то есть имеет форму, которую ожидают официальные библиотеки, поэтому передавайте его им как есть.\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\nБез библиотеки (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**Секрет вебхука.** У каждого ключа API свой секрет (`whsec_...`). Он показывается один раз, при создании ключа в панели в разделе «Разработчикам» (поле `webhook_secret` в ответе на создание ключа). Позже его прочитать нельзя: список ключей никогда его не возвращает. Если вы его потеряли или он мог утечь, сгенерируйте новый в панели в разделе «Разработчикам» (New webhook secret у ключа). Новый секрет тоже показывается только один раз. **Перекрытие при ротации:** в течение 24 часов после ротации (до `previous_secret_expires_at` в ответе на ротацию) каждая доставка подписывается и новым, и предыдущим секретом (две записи `v1,` в webhook-signature), поэтому вы можете переключить эндпоинт на новый секрет, не теряя доставок. Повторная ротация, пока перекрытие ещё действует, отклоняется с кодом 409 WEBHOOK_SECRET_ROTATION_PENDING и `error.details.previous_secret_expires_at` (когда можно выполнить ротацию снова), поэтому одновременно активны не более двух секретов, а секрет, на который получатели ещё могут полагаться, никогда не выводится из употребления досрочно. Если секрет утёк, отзовите ключ и создайте новый. Отзыв ключа удаляет оба секрета. Доставки используют секреты ключа на момент отправки, в том числе доставки для работы, начатой до ротации. У запроса из сеанса в панели с выполненным входом нет ключа API и, следовательно, нет секрета вебхука, поэтому `webhook_url` в таком запросе отклоняется с кодом 400 `WEBHOOK_REQUIRES_API_KEY` (снимки, пакетные запросы, зачернения, заверения хешей, создание или изменение снимка по расписанию). Запуски по расписанию используют секрет ключа, создавшего расписание. Если ключ отозван или его секрет не удаётся прочитать в момент доставки, событие не отправляется, а попытка записывается в журнал с указанием причины (прочитать её можно через `GET /webhook-events/{capture_id}`).\n\n**Доставка.** Тайм-аут каждой попытки 10 секунд. Перенаправления никогда не выполняются: ответ 3xx считается ошибкой, как и ответ вне диапазона 2xx или тайм-аут. Тело ответа не читается. **Повторные попытки:** доставка, которая завершилась по тайм-ауту, не смогла подключиться или получила ответ вне диапазона 2xx (кроме 410 Gone), отправляется снова примерно через 5 минут, 30 минут, 2 часа, 8 часов и 24 часа (попытки со 2 по 6, всего около 35 часов), каждый раз с тем же `webhook-id` и новым `webhook-timestamp`. После этого доставка прекращается. Повторная доставка содержит те же данные БЕЗ 24-часовой ссылки для скачивания (`package_url`, `package_url_expires_at`): получите пакет через GET /captures/{capture_id}. Доставки для снимка, удалённого безвозвратно за это время, отбрасываются. При временном сбое чтения секрета подписи на нашей стороне доставка повторяется так же. После ответа 410 Gone, перенаправления, при заблокированном адресе, а также при отсутствующем или отозванном ключе доставка не повторяется. Тело повторной попытки может отличаться от первой (без ссылки для скачивания): выполняйте дедупликацию по webhook-id, а не по хешу тела. Отвечайте кодом 2xx быстро. В журнале попыток записывается общий класс ошибки (connection_failed, timeout, blocked_address, redirect_not_followed, http_<status>), но никогда не исходная сетевая ошибка. certify.completed и redaction.completed отправляются асинхронно, вскоре после возврата ответа. Каждая попытка записывается в журнал; прочитать его можно через `GET /webhook-events/{capture_id}`.\n\n**События**\n\n`capture.completed`: снимок (одиночный, элемент пакетного запроса или запуск расписания по интервалу или cron) завершён. Подписки аккаунта на вебхуки также получают это событие для базового снимка и снимков изменений расписания on_change (собственный webhook_url расписания получает для них monitor.change_detected). `capture` содержит те же поля, что и ответ `GET /captures/{capture_id}` для завершённого снимка, а также `timestamps.eidas`, если была добавлена квалифицированная метка времени, `schedule_id` (null вне расписания), `test_mode` и `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`: снимок не удалось завершить. `error.message` содержит краткую публичную причину (для непредвиденного сбоя \"The capture could not be completed.\"); внутренние подробности ошибки никогда не отправляются. Сюда относится и запуск, который аварийно завершился или превысил время ожидания: его помечает как failed проверка, выполняемая примерно через 30 минут после начала, и тогда же отправляется capture.failed (сообщение \"Capture did not finish (timed out).\"). Та же проверка отправляет certify.failed или redaction.failed для незавершённого заверения хеша или зачернения. Запуск, который завершился уже после того, как о нём было сообщено как о неудачном, не оплачивается и не отправляет 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`: заверение хеша завершено. `certificate` содержит те же поля, что и синхронный ответ `POST /certify-hash`. Доставляется асинхронно, вскоре после возврата ответа.\n\n`certify.failed`: заверение хеша не удалось завершить.\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`: зачернение завершено. `redaction` содержит те же поля, что и ответ `POST /captures/{capture_id}/redact`.\n\n`redaction.failed`: зачернение не удалось завершить. `error.code` содержит код, который вернул бы запрос (например, REDACTION_LEAK_DETECTED), и `details`, если они есть; зачернение, помеченное проверкой как failed, содержит 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`: расписание `on_change` обнаружило изменение текста страницы и сделало новый снимок.\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` и `metadata_verdict` (с 2026-10-14) повторяют результаты сравнения по категориям из compare.diff_summary (null, если сравнения нет). Расписание с `alert_on: \"text\"` отправляет monitor.change_detected, только если text_verdict равен changed (или если результата сравнения нет).\n\n`capture.anchor_confirmed` (только подписки и GET /events): привязка доказательства OpenTimestamps из пакета к блоку Bitcoin подтверждена, обычно через несколько часов после снимка.\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` (только подписки и GET /events): ProofSnap приостановил расписание. `reason` принимает значения `no_billing` (нет доступа к API), `plan_downgrade` (тариф допускает меньше расписаний, не допускает on_change или создание новых задач), `failures` (пять неудачных запусков подряд), `key_revoked` или `invalid_cron`. Приостановка, выполненная вами самостоятельно, событием не является.\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` принимает значения `ok` (compare содержит сравнение), `no_baseline` (нет более раннего снимка для сравнения) или `failed` (сравнение не удалось; compare равен null)."
    },
    {
      "name": "Публичный доступ",
      "description": "Операции, для которых не нужен ключ API."
    },
    {
      "name": "Импорт пакета",
      "description": "Импортируйте в свой аккаунт существующие пакеты доказательств ProofSnap (ZIP-файлы, созданные расширением ProofSnap: снимки веб-страниц, многовкладочные сеансы, одиночные и сводные заверения файлов; или этим API: снимки, заверения, сравнения, зачернения), чтобы с ними работали так же, как с вашими снимками через API: они хранятся 10 лет и доступны для юридического удержания, дел и экспорта.\n\nТолько тарифы Legal (legal_solo, legal_seats), с рабочим ключом API или сеансом в панели с привязанной оплатой (тестовый ключ, сеанс без оплаты и sandbox получают 403 IMPORT_NOT_ALLOWED). При неподтверждённом адресе электронной почты возвращается 403 EMAIL_NOT_VERIFIED, при подписке с просроченным платежом 402 PAYMENT_REQUIRED. Импорт не является снимком: он не учитывается в месячных лимитах и не облагается платой за импорт; оплачивается только как хранимые данные.\n\n**Порядок работы.** 1. `POST /imports` с именем файла и его точным размером в байтах (не более 2 ГиБ): ответ содержит по одному предподписанному URL на каждую часть размером 32 МиБ (последняя часть меньше), действительному 1 час. 2. Отправьте байты каждой части методом PUT на её URL и сохраните заголовок ответа `ETag` для каждой. 3. Вызовите `POST /imports/{import_id}/complete` со всеми номерами частей и их ETag. 4. Опрашивайте `GET /imports/{import_id}`, пока статус не станет окончательным.\n\n**Статусы.** `uploading` (ожидается загрузка частей и вызов complete; загрузка, не завершённая в течение 24 часов, переходит в `expired`), `verifying` (пакет проверяется), `accepted` (проверка пройдена: `capture_id` указывает на новый снимок, его идентификатор совпадает с идентификатором импорта), `rejected` (проверка не пройдена: `reasons` перечисляет коды; сохраняется только запись об импорте), `duplicate` (в вашем аккаунте уже есть снимок с тем же манифестом: `duplicate_of`), `expired`.\n\n**Проверка.** Строгая: подпись RSA для manifest.json, наличие каждого указанного файла с указанным SHA-256 (а также SHA-512 и размером, если они указаны), отсутствие файлов, не указанных в манифесте (кроме файлов, добавляемых операционной системой, например .DS_Store, и доказательств, производных от манифеста), хотя бы один хешированный файл, согласованный идентификатор доказательства, пересчитанная цепочка хешей криминалистического журнала, доказательство OpenTimestamps относительно манифеста (офлайн) и квалифицированная метка времени eIDAS относительно закреплённого корневого сертификата Disig с данными проверки из пакета. Структура ZIP проверяется до чтения содержимого (без шифрования, без ZIP64, без ссылок, безопасные имена, без повторяющихся имён, размеры и CRC-32 каждой записи). Тестовый пакет API (с водяным знаком) отклоняется. Пакеты API, опечатанные до того, как evidence.pdf стал хешироваться, принимаются, а этот файл указывается в `verification.checks.files.unhashed`.\n\n**Уровни** (`summary`): `integrity` (verified или failed), `time_anchor` (eidas_qualified, eidas_qualified_revocation_unknown, ots_bitcoin_attested_unconfirmed, ots_pending, none) и `provenance`: для пакетов API `issued_by_proofsnap` (манифест есть в реестре выпущенных пакетов API) или `unverified`; для пакетов расширения `registered_by_extension_account` (вошедший в систему пользователь расширения зарегистрировал этот манифест при его создании) или `not_registered`; `unknown`, если проверку выполнить не удалось. Уровень provenance никогда не приводит к отказу в импорте.\n\n**Снимок.** Принятый импорт становится снимком с `source` \"import\": его можно скачать (загруженный ZIP байт в байт) и проверить (POST /captures/{capture_id}/verify повторно выполняет те же проверки), на него можно установить юридическое удержание, переместить его в корзину и удалить безвозвратно, он отображается в делах (при импорте с `matter_id` журнал цепочки хранения доказательств дела записывает `package.imported`) и в экспортах и хранится 10 лет с момента импорта. Его `captured_at` равно времени, которое указано в самом пакете (`import.captured_at_basis` \"package_claim\"), а не времени, которое наблюдал ProofSnap; `created_at` равно времени создания загрузки. Сравнения и зачернения импортированного пакета отклоняются (422 IMPORT_NOT_SUPPORTED).\n\n**Ограничения.** Не более 3 одновременно выполняемых импортов (uploading или verifying) на аккаунт (409 IMPORT_LIMIT), 50 созданных импортов на аккаунт за сутки по UTC (429 RATE_LIMITED, `details.action` \"import_create\"; учитывается каждый запрос на создание, включая впоследствии отклонённые) и поминутное ограничение частоты запросов тарифа для create и complete. Объём импорта: не более 5 ГиБ за сутки по UTC и 20 ГиБ за месяц по UTC заявленных размеров пакетов на аккаунт, учитывается при create (и при complete для более старой загрузки); при превышении возвращается 429 IMPORT_DAILY_BYTES_LIMIT или IMPORT_MONTHLY_BYTES_LIMIT с `Retry-After`; отменённая загрузка не учитывается."
    }
  ],
  "paths": {
    "/captures": {
      "post": {
        "tags": [
          "Снимки"
        ],
        "operationId": "createCapture",
        "summary": "Сделать снимок URL",
        "description": "Принимает запрос на снимок и сразу возвращает 202; снимок выполняется в фоновом режиме. Опрашивайте `GET /captures/{capture_id}` или передайте `options.webhook_url`. Имя хоста в URL разрешается и проверяется до приёма запроса.\n\nЧастота запросов ограничивается для каждого аккаунта в окнах по 60 секунд (все ключи API аккаунта и его сеанс в панели используют одно окно). После того как запрос прошёл через ограничитель, ответы этого маршрута содержат заголовки `RateLimit-Policy` и `RateLimit` (на основе IETF draft-ietf-httpapi-ratelimit-headers), а также устаревшие `X-RateLimit-Limit`, `X-RateLimit-Remaining` и `X-RateLimit-Reset`. Ответ 429 дополнительно содержит `Retry-After`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CaptureRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Принято. Идемпотентный повтор более раннего запроса с тем же Idempotency-Key также возвращает 202 с `Idempotent-Replay: true` и исходным телом.",
            "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 или INVALID_EVIDENCE_PACK_OPTIONS. WEBHOOK_REQUIRES_API_KEY: webhook_url отправлен из сеанса в панели (для вебхуков нужен ключ 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 (исчерпан месячный максимум или лимит sandbox; ответ содержит заголовки X-Quota-* или X-Sandbox-*) или 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 с тестовым ключом (ключ ps_live_ аккаунта без привязанного платёжного аккаунта работает как тестовый). EMAIL_NOT_VERIFIED: сеанс в панели с неподтверждённым адресом электронной почты. PLAN_ARCHIVE_ONLY: тариф только сохраняет архив, новые задачи на нём не создаются. PLAN_API_READ_ONLY: ключ API аккаунта на тарифе Website Archive (его ключ только для чтения; используйте панель). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит адрес, не принадлежащий панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности).",
            "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 или 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: слишком много запросов в текущем 60-секундном окне. `error.details.retry_after_seconds` дублирует Retry-After. RateLimit сообщает r=0 и число секунд до сброса окна. Также RATE_LIMITED для сеанса в панели на тарифе Website Archive: его лимит в 100 единиц за сутки по UTC, общий для одиночных и пакетных снимков (одна единица за URL), compare, redact и certify-hash (одна единица за запрос); запрос, который не укладывается в остаток, отклоняется целиком (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After и details.retry_after_seconds указывают число секунд до следующих суток по UTC, а поминутные заголовки RateLimit и X-RateLimit-* не передаются). Короткий отказ, в котором есть только details.retry_after_seconds и Retry-After: 1, означает одновременное обновление счётчика: повторите запрос. При отказе не по вине вызывающей стороны единицы возвращаются; за работу, принятую с кодом 202 и позже завершившуюся ошибкой, единица не возвращается. Это мягкое ограничение, а не граница безопасности. Одиночный снимок, для которого ответ по Idempotency-Key выдаётся из хранилища (повтор, 409 или 422), обрабатывается до проверки суточного лимита и единицу не расходует.\n\nCONCURRENT_CAPTURE_LIMIT: у аккаунта одновременно выполняется слишком много снимков (developer 10, startup 25, growth 100, legal_solo и legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` и Retry-After указывают, когда повторить попытку.\n\nTEST_MODE_DAILY_LIMIT: работа в тестовом режиме (тестовый ключ или сеанс без привязанной оплаты) ограничена 200 единицами на аккаунт за сутки по UTC; `details.used`, `details.limit`, `details.reset_at`, заголовки `X-Test-Daily-*` и 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 (не удалось запустить снимок; details.capture_id) или 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": [
          "Снимки"
        ],
        "operationId": "listCaptures",
        "summary": "Список ваших снимков",
        "description": "Созданные вами записи, сначала новые, в любом статусе (включая удалённые, то есть корзину; фильтруйте по status). Каждая строка содержит source, redacted_from, label и compared, поэтому её тип можно показать без повторного чтения. Снимок, сделанный для компании, выводится в списке, только пока вы ещё можете с ним работать (у вас есть место в этой компании или вы её владелец); поэтому страница может содержать меньше строк, чем limit, а next_cursor продолжает список после последней прочитанной строки. Сертификаты, сравнения и зачернения включены.\n\nПоиск (только в собственном списке): url, host или schedule_id в сочетании с from / to и status находят запись о странице за определённый день. Некорректный фильтр, одновременная передача двух параметров из url / host / schedule_id или from не раньше to приводят к ответу 400 MALFORMED_REQUEST.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Размер страницы, от 1 до 200 (по умолчанию 50). Значение вне диапазона возвращает 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "next_cursor с предыдущей страницы (формат created_at|capture_id или completed_at|capture_id при order=completed_at). Передавайте его без изменений.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Только строки с этим статусом: processing, completed, failed или deleted (корзина). Любое другое значение возвращает 400 MALFORMED_REQUEST.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "url",
            "in": "query",
            "required": false,
            "description": "Только снимки именно этого URL в том виде, в котором он был снят (http или https). Не более одного из url, host и schedule_id.",
            "schema": {
              "type": "string",
              "maxLength": 2048
            }
          },
          {
            "name": "host",
            "in": "query",
            "required": false,
            "description": "Только снимки, URL которых находится на этом хосте (без учёта регистра, например www.example.com; поддомены считаются отдельными хостами). Не более одного из url, host и schedule_id. Строки, созданные до 2026-10-05, содержат хост после дозаполнения данных.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "schedule_id",
            "in": "query",
            "required": false,
            "description": "Только снимки, сделанные этим расписанием. Не более одного из url, host и schedule_id.",
            "schema": {
              "type": "string",
              "pattern": "^sch_[A-Za-z0-9]{8,32}$"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Создано в этот момент или позже: дата (YYYY-MM-DD, полночь по UTC) или метка времени ISO 8601.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Создано до этого момента (не включая его): дата (YYYY-MM-DD, полночь по UTC) или метка времени ISO 8601. Должно быть позже from. С schedule_id или url самая новая строка до указанного момента является версией, актуальной на тот момент.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "order",
            "in": "query",
            "required": false,
            "description": "created_at (по умолчанию: сначала последние созданные) или completed_at (сначала последние завершённые; только при status=completed, иначе 400 MALFORMED_REQUEST). При completed_at параметры from и to ограничивают completed_at, а next_cursor имеет вид completed_at|capture_id. Работает вместе с url, host и schedule_id.",
            "schema": {
              "type": "string",
              "enum": [
                "created_at",
                "completed_at"
              ],
              "default": "created_at"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Страница снимков.",
            "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": "Когда снимок был завершён (null во время обработки или после сбоя)."
                          },
                          "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": "Только для строк completed. То же, что external_archive в GET /captures/{capture_id}."
                          },
                          "label": {
                            "type": "string",
                            "nullable": true,
                            "description": "Метка записи (сертификаты: заданная метка; зачернения: какой снимок они зачерняют)."
                          },
                          "compared": {
                            "type": "array",
                            "nullable": true,
                            "items": {
                              "type": "string"
                            },
                            "description": "Только для сравнений: ID двух исходных снимков."
                          },
                          "source": {
                            "type": "string",
                            "nullable": true,
                            "description": "Как была создана строка: api (снимок веб-страницы), schedule, certify-hash (сертификат), compare (сравнение) или redact (зачернение); null у строк, созданных до появления этого поля."
                          },
                          "redacted_from": {
                            "type": "string",
                            "nullable": true,
                            "description": "Только для зачернений: снимок, из которого сделано зачернение."
                          },
                          "captured_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Когда был сделан снимок страницы (завершённые записи). Для зачернения это время снимка-источника (redacted_at указывает, когда было выполнено зачернение)."
                          },
                          "redacted_at": {
                            "type": "string",
                            "format": "date-time",
                            "description": "Только для зачернений: когда было сделано зачернение. captured_at у зачернения совпадает с моментом съёмки исходного снимка."
                          },
                          "compared_at": {
                            "type": "string",
                            "nullable": true,
                            "format": "date-time",
                            "description": "Только для сравнений: когда выполнено сравнение. Его captured_at содержит то же значение (оставлено для старых клиентов); используйте compared_at."
                          },
                          "manifest_hash": {
                            "type": "string",
                            "nullable": true,
                            "description": "sha256:<hex> файла manifest.json пакета (строки completed)."
                          },
                          "private_note": {
                            "type": "string",
                            "nullable": true,
                            "description": "Ваша личная заметка к снимку (см. PrivateNote), никогда не входит в пакет доказательств."
                          },
                          "legal_hold": {
                            "type": "boolean",
                            "description": "На снимок установлено юридическое удержание (пока true, снимок не удаляется; снимается автоматически, когда после окончания подписки прошло 30 дней). См. PATCH /captures/{capture_id}."
                          }
                        }
                      }
                    },
                    "next_cursor": {
                      "type": "string",
                      "description": "null, если на этой странице меньше строк, чем limit.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: недопустимый cursor, status (одно из processing, completed, failed, deleted), limit (от 1 до 200) или фильтр поиска (url, host, schedule_id, from, to; не более одного из url / host / schedule_id; from раньше, чем 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": [
          "Снимки"
        ],
        "operationId": "createBatch",
        "summary": "Сделать снимки нескольких URL",
        "description": "Запускает по одному снимку на каждый URL с одинаковыми параметрами. Каждый снимок независим: опрашивайте каждый capture_id или получайте по одному вебхуку на каждый снимок. Расходует одну единицу ограничения частоты запросов и одну единицу месячного лимита на каждый URL. Запрос с большим числом URL, чем допускает одно окно ограничения частоты запросов, никогда не может быть выполнен, поэтому он отклоняется с ошибкой 400 RATE_LIMIT_COST_TOO_HIGH, а не 429. Разделите его на запросы меньшего размера. Повтор завершённого запроса (тот же Idempotency-Key и то же тело) обслуживается до применения ограничения частоты запросов и не расходует единицу ограничения.\n\nОграничение частоты запросов действует на уровне аккаунта в окнах по 60 секунд (все ключи API аккаунта и его сеанс в панели используют одно окно). После того как запрос прошёл через ограничитель, ответы этого маршрута содержат заголовки `RateLimit-Policy` и `RateLimit` (на основе черновика IETF draft-ietf-httpapi-ratelimit-headers), а также устаревшие `X-RateLimit-Limit`, `X-RateLimit-Remaining` и `X-RateLimit-Reset`. Ответ 429 дополнительно содержит `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": "Принято. Идемпотентный повтор завершённого пакета с тем же Idempotency-Key и телом также возвращает 202 с `Idempotent-Replay: true`; он обслуживается до применения ограничения частоты запросов, поэтому не расходует единицу лимита.",
            "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 и received либо index, url и вложенный объект reason для недопустимого URL), MALFORMED_REQUEST или ошибка options. WEBHOOK_REQUIRES_API_KEY: webhook_url передан из сеанса в панели (для вебхуков нужен ключ API). RATE_LIMIT_COST_TOO_HIGH: URL больше, чем допускает одно окно ограничения частоты запросов; 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 или PAYMENT_REQUIRED. Пакетный запрос также отклоняется с QUOTA_EXCEEDED, если в этот момент не удаётся прочитать использование за месяц; повторите запрос.",
            "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 с тестовым ключом. EMAIL_NOT_VERIFIED: сеанс в панели с неподтверждённым адресом электронной почты. PLAN_ARCHIVE_ONLY: тариф только сохраняет архив, новые задачи на нём не создаются. PLAN_API_READ_ONLY: ключ API аккаунта на тарифе Website Archive (его ключ только для чтения; используйте панель). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит адрес, не принадлежащий панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности).",
            "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: слишком много запросов в текущем 60-секундном окне. `error.details.retry_after_seconds` дублирует Retry-After. RateLimit сообщает r=0 и число секунд до сброса окна. Также RATE_LIMITED для сеанса в панели на тарифе Website Archive: его лимит в 100 единиц за сутки по UTC, общий для одиночных и пакетных снимков (одна единица за URL), compare, redact и certify-hash (одна единица за запрос); запрос, который не укладывается в остаток, отклоняется целиком (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After и details.retry_after_seconds указывают число секунд до следующих суток по UTC, а поминутные заголовки RateLimit и X-RateLimit-* не передаются). Короткий отказ, в котором есть только details.retry_after_seconds и Retry-After: 1, означает одновременное обновление счётчика: повторите запрос. При отказе не по вине вызывающей стороны единицы возвращаются; за работу, принятую с кодом 202 и позже завершившуюся ошибкой, единица не возвращается. Это мягкое ограничение, а не граница безопасности.\n\nCONCURRENT_CAPTURE_LIMIT: ни один URL из пакетного запроса не удалось запустить, потому что у аккаунта уже выполняется максимально допустимое число снимков (developer 10, startup 25, growth 100, legal_solo и legal_seats 25, custom 200, website_archive 25, sandbox 5); `details.limit`, `details.in_flight`, `details.requested` и Retry-After. Если запустить удалось только часть URL, пакетный запрос принимается с кодом 202, а остальные перечисляются в `not_admitted`.\n\nTEST_MODE_DAILY_LIMIT: работа в тестовом режиме (тестовый ключ или сеанс без привязанной оплаты) ограничена 200 единицами на аккаунт за сутки по UTC, по одной за URL; `details.used`, `details.limit`, `details.reset_at`, заголовки `X-Test-Daily-*` и 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": [
          "Снимки"
        ],
        "operationId": "listCaptureHosts",
        "summary": "Список хостов, которые вы снимали",
        "description": "Все имена хостов из вашего списка снимков с количеством снимков и временем последнего снимка по каждому хосту, для фильтра по домену: передайте хост обратно как GET /captures?host=. count равно числу строк, которые возвращает GET /captures?host=<host> без status (среди прочитанных снимков): тестовые и рабочие снимки вместе, все статусы, включая снимки в корзине (status deleted), так, как их показывает список; безвозвратно удалённые снимки и строки без URL страницы (сертификаты) не учитываются. Снимок учитывается, когда у него есть сохранённое имя хоста (у снимков, созданных до 2026-10-05, оно появляется после дозаполнения данных). Снимок компании учитывается, только пока вы можете с ним работать (у вас есть место в этой компании или вы её владелец). host указывается в нижнем регистре. Сортировка по count (сначала наибольшие), затем по host (от A до Z). Не более 1000 хостов. Читаются 5000 самых новых снимков; truncated равно true, если хостов больше 1000 или есть снимки старше этих 5000, поэтому хост, снятый только раньше них, может отсутствовать. Список вычисляется не чаще одного раза в 30 секунд на аккаунт и может отставать от актуального состояния до 30 секунд (чуть больше, пока его вычисляет другой запрос аккаунта). Вычисление расходует одну единицу вашего поминутного ограничения частоты запросов; ответ из этого кеша ничего не стоит. Доступно на всех тарифах, для ключей API и сеанса в панели.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Имена хостов.",
            "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": "Имя хоста в нижнем регистре, значение, которое принимает GET /captures?host=."
                          },
                          "count": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 12,
                            "description": "Снимки этого хоста среди прочитанных снимков: строки, которые возвращает GET /captures?host= без status, включая находящиеся в корзине."
                          },
                          "last_captured_at": {
                            "type": "string",
                            "format": "date-time",
                            "nullable": true,
                            "example": "2026-10-05T07:48:38Z",
                            "description": "Время последнего снимка этого хоста (когда страница была снята, иначе когда снимок был запрошен), UTC, с точностью до секунды."
                          }
                        }
                      }
                    },
                    "truncated": {
                      "type": "boolean",
                      "description": "true, если список может быть неполным: более 1000 хостов или больше снимков, чем прочитанные 5000 самых новых."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: значение scope не поддерживается (только собственные снимки).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "description": "RATE_LIMITED: поминутное ограничение частоты запросов исчерпано (вычисление списка стоит одну единицу) или список вашего аккаунта сейчас вычисляется другим запросом, а более раннего результата не сохранено; повторите запрос через 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/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "get": {
        "tags": [
          "Снимки"
        ],
        "operationId": "getCapture",
        "summary": "Получить снимок",
        "description": "Статус и, после завершения, новая ссылка package_url, действительная до 24 часов (см. package_url_expires_at). Работает для идентификаторов снимков, сертификатов, сравнений и зачернений. Доступно для чтения аккаунту, который создал запись, участникам дела, к которому она относится, и владельцу компании, для которой она сделана (если подписка компании закончилась, до закрытия её окна скачивания).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "event",
            "in": "query",
            "required": false,
            "description": "Передавайте download, когда скачиваете пакет: для снимка в деле в журнал доступа дела записывается событие capture.downloaded. При обычном опросе статуса этот параметр не передавайте.",
            "schema": {
              "type": "string",
              "enum": [
                "download"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Снимок.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CaptureResult"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: снимок вам не принадлежит.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND: неизвестный или некорректный ID, удалённый снимок либо файл пакета не найден.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Снимки"
        ],
        "operationId": "deleteCapture",
        "summary": "Удалить снимок",
        "description": "По умолчанию перемещает снимок в корзину. Его можно восстановить через PATCH до restorable_until (30 дней); после этого он удаляется при очистке корзины. С permanent=true сохранённый пакет сразу удаляется безвозвратно, а запись сокращается до служебной записи-заглушки (tombstone); отменить это нельзя. Ни то, ни другое не удаляет уже скачанные вами пакеты и доказательство OpenTimestamps. Снимок в открытом деле или деле в архиве сначала удаляется из дела (это записывается в журнал цепочки хранения доказательств дела), если вызывающий может открыть это дело; вызывающий, который не может (автор снимка после его удаления из дела), получает 409 MATTER_LINKED, как для корзины, так и для permanent=true, пока владелец дела не удалит снимок из дела; снимок в опечатанном деле удалить нельзя. Снимок, который ещё обрабатывается, удалить нельзя (409 CAPTURE_PROCESSING); удалите его, когда он перейдёт в статус completed или failed. Удаление снимка, который так и не завершился, возвращает его месячную единицу.\n\nЛьготный период после снятия юридического удержания: в течение 14 дней после снятия юридического удержания (`deletion_blocked_until` в GET) снимок не удаляется безвозвратно (409 CAPTURE_HOLD_GRACE с `details.until`) ни человеком, ни очисткой корзины, ни по сроку хранения (удаление аккаунта и компании после окончания подписки этого не ждёт). Перемещение в корзину разрешено; её очистка ждёт окончания льготного периода. Безвозвратное удаление сначала снимает юридическое удержание S3 Object Lock, ещё оставшееся на сохранённых объектах; если хранилище отказывает, запрос возвращает 409 CAPTURE_LEGAL_HOLD (\"still locked in storage\", повторите позже), и ничего не удаляется. Каждое перемещение в корзину, восстановление и безвозвратное удаление записывается в цепочку событий снимков аккаунта (GET /captures/events). Пакет с зачернёнными данными или сравнение, исходный снимок которого находится под юридическим удержанием, при перемещении в корзину или удалении возвращает `source_under_legal_hold: true`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "permanent",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Удалено. Корзина: { capture_id, status: \"deleted\", restorable: true, restorable_until }. Безвозвратно: { capture_id, status: \"deleted\", permanent: true }. Уже в корзине: { 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": "Присутствует (true) только для зачернения или сравнения, сделанного из снимка, который находится под юридическим удержанием."
                    },
                    "deletion_blocked_until": {
                      "type": "string",
                      "format": "date-time",
                      "description": "Корзина в льготный период после снятия удержания: очистка корзины ждёт до этого момента."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: снимок вам не принадлежит. FORBIDDEN также возвращается, если снимок относится к неопечатанному делу, а вызывающий (владелец компании, работающий со снимком участника компании, а не его автор) не может открыть это дело. PLAN_API_READ_ONLY: ключ API аккаунта Website Archive (его ключ только для чтения; используйте панель).",
            "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: снимок является приложением опечатанного дела. MATTER_LINKED: снимок является приложением дела, в котором вызывающий больше не состоит; сначала его должен убрать владелец дела. CAPTURE_PROCESSING: снимок ещё обрабатывается (error.details.status). CAPTURE_LEGAL_HOLD: снимок находится под юридическим удержанием, и никто не может ни переместить его в корзину, ни удалить безвозвратно. CAPTURE_HOLD_GRACE: безвозвратное удаление в льготный период после снятия юридического удержания со снимка (LEGAL_HOLD_RELEASE_GRACE_DAYS, 14 дней); error.details.until сообщает, когда оно станет возможным. Перемещение в корзину в льготный период разрешено.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "INTERNAL_ERROR: сохранённый пакет не удалось удалить безвозвратно; ничего не удалено.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Снимки"
        ],
        "operationId": "restoreCapture",
        "summary": "Восстановить удалённый снимок, задать его личную заметку либо установить или снять юридическое удержание",
        "description": "Восстанавливает снимок из корзины. Если при удалении снимок был исключён из дела, он возвращается в это дело, когда оно всё ещё открыто и вызывающая сторона по-прежнему может его открыть; только в этом случае ответ содержит matter_id и matter_relinked (matter_id равно null, если повторная привязка не состоялась). Вызывающая сторона, которая больше не может открыть это дело, получает снимок обратно без него, в журнал цепочки хранения доказательств дела ничего не записывается, и ответ не упоминает дело. Снимок, который не находится в корзине, возвращается без изменений.\n\nЛичная заметка: тело с `private_note` (строка не длиннее 200 символов или null, чтобы удалить заметку) вместо этого задаёт личную заметку снимка и больше ничего не делает; его нельзя совмещать с `action`. Заметка хранится только в записи снимка, вне опечатанного пакета доказательств, поэтому её изменение никогда не затрагивает доказательства и их хеши. Задать её может только автор снимка (владелец компании и участники дела получают 403) для снимка в любом состоянии, кроме безвозвратно удалённого (404). Ответ: { capture_id, status, private_note }.\n\nЮридическое удержание: тело `{ \"action\": \"legal_hold\", \"enabled\": true|false, \"reason\"?: string }` устанавливает (true) или снимает (false) юридическое удержание снимка и больше ничего не делает. Снимок под юридическим удержанием не удаляется: DELETE (в корзину или безвозвратно) возвращает 409 CAPTURE_LEGAL_HOLD для всех, и ни очистка корзины, ни удаление по истечении срока хранения его не затрагивают; GET показывает `storage.retention.earliest_deletion_at` равным null. Удержание сохраняется, только пока у аккаунта есть тариф с хранением данных (любой платный тариф, включая Archive only; для снимка компании: компания активна или сохранена как архив компании). После окончания подписки снимки остаются доступными 30 дней; если до этого срока такой тариф не куплен, в этот день все юридические удержания аккаунта (или компании) снимаются автоматически (исполнитель system, причина subscription_ended, записывается как любое снятие, без льготного периода после снятия), и снимки удаляются вместе с остальными. Чтобы сохранить удерживаемые снимки, до этой даты перейдите на Archive only (или купите архив компании) либо скачайте их. В течение этих 30 дней новое удержание установить нельзя (у аккаунта нет платного тарифа), но существующие удержания снять можно. Удержание хранится только в записи снимка, вне опечатанного пакета доказательств. `reason` необязателен: это простой текст не длиннее 200 символов (без управляющих, невидимых символов и символов направления текста; повторяющиеся пробельные символы заменяются одним, пустое значение означает отсутствие причины); причина хранится вместе с удержанием и удаляется при его снятии.\n\nКто может: установить удержание может любой, кто вправе выполнять действия со снимком (его автор, для снимка компании пока у него есть место в этой компании, и владелец компании, в том числе компании, подписка которой закончилась, пока она находится в пределах срока хранения или сохранена как архив компании; для архива только через сеанс в панели). Снять его: для личного снимка его автор, для снимка компании только владелец компании (участник компании получает 403 LEGAL_HOLD_OWNER_ONLY). Для установки удержания нужен платный тариф (любой платный тариф, включая Archive only): бесплатный sandbox, а также аккаунт с закончившейся подпиской получают 403 LEGAL_HOLD_NOT_AVAILABLE с details.reason \"plan\" (действующий архив компании считается платным для снимков этой компании при работе через сеанс в панели её владельца), а снимок в тестовом режиме (с водяным знаком, сделанный тестовым ключом или без привязанной оплаты) получает 403 LEGAL_HOLD_NOT_AVAILABLE с details.reason \"test_capture\". Снять удержание можно на любом тарифе, в том числе после окончания подписки. На Website Archive ключ API работает только для чтения (403 PLAN_API_READ_ONLY), а через сеанс в панели это возможно. Просроченный платёж или неоплаченная подписка этому не мешают. Сеанс в панели или ключ, адрес электронной почты которых не подтверждён, получают 403 EMAIL_NOT_VERIFIED.\n\nЗапрос отклоняется для снимка в корзине (409 CAPTURE_IN_TRASH: сначала восстановите его) или ещё обрабатываемого снимка (409 CAPTURE_PROCESSING); для безвозвратно удалённого снимка или снимка, безвозвратное удаление которого уже выполняется, возвращается 404. Каждый запрос на юридическое удержание стоит одну единицу поминутного ограничения частоты запросов аккаунта (429 RATE_LIMITED с Retry-After, когда лимит исчерпан). Передача состояния, которое у снимка уже есть, ничего не меняет и возвращает 200 с сохранёнными полями и `changed: false`. Каждое изменение записывается в журнал аудита как capture.legal_hold (для снимка компании в тенанте компании), фиксируется в истории доступа к снимку (`legal_hold_on` / `legal_hold_off`, сохраняется и после последующего безвозвратного удаления снимка) и, если снимок входит в дело, записывается в журнал цепочки хранения доказательств дела как capture.legal_hold_placed или capture.legal_hold_released с ID снимка в качестве цели (причина попадает в журнал доступа дела и никогда не попадает в хешируемую цепочку; цепочка опечатанного дела не меняется). Ответ: { capture_id, legal_hold, legal_hold_set_at, legal_hold_reason, changed }.\n\nЮридическое удержание v2: установка или снятие удержания также устанавливает или снимает юридическое удержание S3 Object Lock для каждой версии сохранённого пакета, его обновлённого доказательства OpenTimestamps и отдельно хранящейся копии (`s3_legal_hold` в GET; если хранилище отказывает или у него нет конфигурации Object Lock, удержание в записи снимка всё равно действует, а неудачную попытку сервер повторяет). После снятия начинается льготный период 14 дней, в течение которого снимок не удаляется безвозвратно (но не после окончания подписки: тогда вместо него действуют 30 дней после окончания). Удержание, установленное юридическим удержанием дела (`legal_hold_source` \"matter:<id>\"), снимается на уровне дела, пока действует удержание дела: попытка снять его здесь возвращает 409 LEGAL_HOLD_MATTER (error.details.matter_id для того, кто может читать дело). Каждое изменение записывается в цепочку событий снимков аккаунта (GET /captures/events).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "restore (по умолчанию для тела без private_note) или legal_hold (с enabled и необязательным reason).",
                    "enum": [
                      "restore",
                      "legal_hold"
                    ]
                  },
                  "private_note": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/PrivateNote"
                      }
                    ],
                    "description": "Задать (строка) или очистить (null или пустая строка) личную заметку. Нельзя сочетать с action."
                  },
                  "enabled": {
                    "type": "boolean",
                    "description": "При action legal_hold (там обязателен): true устанавливает юридическое удержание, false снимает его."
                  },
                  "reason": {
                    "type": "string",
                    "nullable": true,
                    "maxLength": 200,
                    "description": "При action legal_hold, необязательно: причина удержания снимка, простой текст не длиннее 200 символов. Хранится вместе с удержанием, вне пакета доказательств; при снятии удержания игнорируется (тогда попадает только в журнал аудита)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Снимок восстановлен ({ capture_id, status, restored: true, matter_id?, matter_relinked? }), не изменён ({ capture_id, status }), личная заметка задана ({ capture_id, status, private_note }) или состояние юридического удержания задано ({ 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": "При action legal_hold: удержание снимка после запроса."
                    },
                    "legal_hold_set_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "При action legal_hold: когда было установлено текущее удержание (null, если удержания нет)."
                    },
                    "legal_hold_reason": {
                      "type": "string",
                      "nullable": true,
                      "description": "При action legal_hold: причина, сохранённая с текущим удержанием, или null."
                    },
                    "changed": {
                      "type": "boolean",
                      "description": "При action legal_hold: false, если снимок уже был в запрошенном состоянии (ничего не изменилось и не было записано в журнал)."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: недопустимый JSON, тело, которое не является объектом, action, отличное от restore или legal_hold, private_note вместе с action, или private_note, которое не является строкой или null, длиннее 200 символов или содержит управляющие, невидимые символы или символы направления текста. Для legal_hold: enabled отсутствует или не является логическим значением, reason не является строкой, длиннее 200 символов или не является простым текстом, или неизвестное поле.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: снимок вам не принадлежит. FORBIDDEN также возвращается, если снимок относится к неопечатанному делу, а вызывающий (владелец компании, работающий со снимком участника компании, а не его автор) не может открыть это дело. PLAN_API_READ_ONLY: ключ API аккаунта Website Archive (его ключ только для чтения; используйте панель). Для private_note: FORBIDDEN для всех, кроме автора снимка (которому по-прежнему должно быть разрешено с ним работать). Для legal_hold: FORBIDDEN, если вызывающему нельзя работать со снимком; LEGAL_HOLD_OWNER_ONLY, если участник компании (а не её владелец) снимает удержание со снимка компании; LEGAL_HOLD_NOT_AVAILABLE при установке удержания без платного тарифа (details.reason plan) или на снимок в тестовом режиме (details.reason test_capture); EMAIL_NOT_VERIFIED для неподтверждённого адреса электронной почты. Правило о деле, описанное выше, на юридическое удержание не распространяется.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "CAPTURE_NOT_FOUND, в том числе для снимка, удалённого безвозвратно.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Только для юридического удержания. CAPTURE_IN_TRASH: снимок находится в корзине; сначала восстановите его. CAPTURE_PROCESSING: снимок ещё обрабатывается (error.details.status). LEGAL_HOLD_MATTER: удержание снимка установлено юридическим удержанием его дела, которое всё ещё действует; снимите его на уровне дела. LEGAL_HOLD_SOURCE: удержание зачернения или сравнения, которое следует за снимком, из которого оно сделано (для сравнения: за любым из сравниваемых снимков), а этот снимок всё ещё находится под юридическим удержанием (установленным на сам снимок или через его дело): пакет остаётся удержанным, пока это удержание не будет снято (`details.source_capture_id`). Сравнение удерживается, пока удерживается любой из сравниваемых снимков, и освобождается, только когда не удерживается ни один из них (`legal_hold_source` \"derived:<capture id>\" указывает на удерживаемый снимок).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Только для юридического удержания. RATE_LIMITED: поминутное ограничение частоты запросов аккаунта исчерпано. `error.details.retry_after_seconds` повторяет 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": [
          "Снимки"
        ],
        "operationId": "verifyCapture",
        "summary": "Проверить хранимый пакет",
        "description": "Считывает сохранённый пакет и проверяет RSA-подпись manifest.json и SHA-256 каждого перечисленного в нём файла. Для пакетов больше 256 МиБ возвращается 413 VERIFY_PACKAGE_TOO_LARGE; такие пакеты проверяйте офлайн с помощью приложенных скриптов. Каждый запрос стоит одну единицу поминутного ограничения частоты запросов аккаунта (429 RATE_LIMITED). Если пакет содержит manifest.json.tsr, ProofSnap также проверяет токен метки времени eIDAS (checks.eidas): подпись токена и подписанные атрибуты, наличие в нём SHA-256 файла manifest.json, extendedKeyUsage сертификата службы меток времени, цепочку его сертификатов до закреплённого корневого центра сертификации Disig QCA4 на момент времени токена (genTime) и статус сертификата службы меток времени по ответу OCSP или CRL, сохранённому при выпуске в eidas_validation.json (проверяется по подписи, а не запрашивается в реальном времени). Эта проверка никогда не запрашивает новую метку времени. Также сообщается, есть ли в пакете manifest.json.ots и есть ли хеш манифеста в реестре выпущенных пакетов ProofSnap. Вердикт зависит только от подписи манифеста и перечисленных файлов; токен, не прошедший проверку, отражается в checks.eidas и в warnings (EIDAS_TOKEN_FAILED), но на вердикт не влияет. Пакет, выпущенный с квалифицированной меткой времени, содержит eidas_requested: true в подписанном manifest.json; если такой пакет больше не содержит manifest.json.tsr, checks.eidas и checks.eidas_disig сообщаются как отсутствующие с предупреждением EIDAS_TOKEN_MISSING (приложенные скрипты проверки выдают для такого пакета FAIL). Доступно владельцу снимка, участникам его дела и владельцу компании, для которой он сделан. Чтобы проверить метки времени независимо, запустите скрипты из папки verification/ пакета.\n\nИмпортированные пакеты (source import): сохранённый ZIP повторно проходит проверки импорта (подпись, каждый файл, цепочка хранения доказательств, криминалистическая цепочка, метки времени; происхождение для пакетов API заново ищется в реестре выпущенных пакетов API, а для пакетов расширения берётся из записи об импорте). Ответ: { 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 }. Для пакета больше 512 МиБ или если проверка не укладывается примерно в 20 секунд возвращается rechecked: false с причиной (too_large_for_synchronous_check, time_budget, storage_error), verdict: null и stored_verification (отчёт, сохранённый при импорте).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Результат проверки.",
            "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: неизвестный или удалённый снимок либо у снимка нет сохранённого пакета.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "VERIFY_PACKAGE_TOO_LARGE: размер хранимого пакета превышает 256 МиБ.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: поминутное ограничение частоты запросов аккаунта исчерпано.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: не удалось прочитать сохранённый пакет.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/captures/{capture_id}/redact": {
      "parameters": [
        {
          "$ref": "#/components/parameters/CaptureId"
        }
      ],
      "post": {
        "tags": [
          "Сравнение и зачернение"
        ],
        "operationId": "redactCapture",
        "summary": "Создать копию с зачернёнными данными",
        "description": "Создаёт из завершённого снимка новый пакет без выбранного содержимого, заново подписывает его (новый ключ RSA-4096) и отправляет на привязку OpenTimestamps. Оригинал не изменяется. Синхронно: возвращает 201, когда пакет готов. Учитывается как один снимок.\n\n- Зачернение областей закрашивает чёрным участки скриншота. Скриншот JPEG в том виде, в каком его записывает снимок (sequential, grayscale или YCbCr), зачерняется без декодирования в пиксели: сжатые блоки (MCU, от 8 до 32 пикселей по стороне), которых касается область, заменяются чёрными блоками, а все остальные блоки сохраняются без изменений, поэтому остальная часть изображения не пересжимается (`screenshot_method` `jpeg_dct_blocks`, до 150 мегапикселей). Закрашенной зоной считается каждая область, расширенная до целых блоков (`painted_rects`), поэтому она всегда содержит область. Скриншот PNG или JPEG другого типа декодируется, закрашивается и перекодируется в screenshot.jpeg, после чего блоки 8 x 8, которых касается область, закрашиваются чёрным в новом файле (`screenshot_method` `decode_repaint_jpeg`, до 60 мегапикселей). Скриншот сверх своего предела или с нечитаемыми данными изображения отклоняется с ошибкой 422 REDACTION_SCREENSHOT_UNREADABLE до какого-либо списания. Если заголовок читается, а данные изображения нет, ошибка обнаруживается после резервирования месячной единицы, и эта единица освобождается. Зачернение текста и метаданных работает и без областей. redaction_manifest.json (`screenshot_boxes`) фиксирует метод, `painted_rects`, сетку блоков и пояснение для судебных экспертов.\n- Зачернение текста удаляет совпадения из page.html и domtextcontent.txt, из каждого свободного текстового значения metadata.json (заголовок страницы и значения meta, записи WHOIS / RDAP, субъект TLS, значения заголовков HTTP-ответа), а также из каждого свободного текстового значения forensic_log.json и chain_of_custody.json (case_reference, case_id, case_note, ваши значения metadata, внедрённый код и его ошибки, подписи закрытых всплывающих окон, legal_context.note). Идентификаторы, хеши, время и URL в этих записях остаются без изменений. Закодированные вхождения тоже удаляются: ссылки на символы HTML (именованные, десятичные, шестнадцатеричные, например M&uuml;ller или jan&#64;firma.cz), процентное кодирование (mailto:jan%40firma.cz) и экранирование JavaScript (\\u0040, \\x40), включая кодирование с вложенностью до двух уровней; маркер заменяет сами закодированные символы. Проверка после зачернения на сохранившиеся совпадения читает файлы JSON значение за значением и ищет в каждом тексте как в записанном, так и в декодированном виде, поэтому она срабатывает на совпадение в любой из этих форм. Зачернение текста также закрашивает скриншот там, где text_layout.json исходного снимка размещает вхождение (`text_rule_rects`): закрашиваются предполагаемые строки текстового блока, содержащие совпадение, а весь блок целиком только при неуверенной оценке (учитывается в `screenshot_boxes.from_text_rules.rects_whole_block` файла redaction_manifest.json); вхождения, которые не удалось разместить, учитываются в `text_occurrences_not_located`. Правила учитывают точный регистр букв, если не задан `case_insensitive`.\n- Зачернение метаданных удаляет поля из metadata.json. Два имени действуют шире. `ip_dns` (также принимаются `ip`, `dns`, `resolved_ips`) удаляет IP-адреса и ответы DNS целевого сайта из metadata.json, forensic_log.json и chain_of_custody.json (собственные адреса машины снимков остаются), а также удаляет их, только целые адреса, из page.html, domtextcontent.txt и скриншота как автоматические правила для текста (записываются только с источником ip_dns, никогда с самим адресом или его хешем, и не учитываются в лимите text_redactions). `cookie_names` (также `cookie`, `cookies`, `http_headers.cookie`) удаляет заголовки Cookie и Set-Cookie и все списки имён cookie в этих записях; количество сохраняется. Значения cookie зачерняются уже в каждом снимке.\n\nОбласть закрывает только пиксели. Текст под областью остаётся в page.html, domtextcontent.txt и metadata.json, если вы не добавите также зачернение текста, а изображение под областью по-прежнему может быть указано в page.html ссылкой или встроено в него (img src или srcset, CSS url(), data: URI); зачернение текста, совпадающее с его адресом, удаляет эту ссылку. Пакет с зачернёнными данными содержит из оригинала только screenshot.jpeg (или screenshot.png, если ни одна область его не заменила), page.html, domtextcontent.txt, metadata.json и forensic_log.json, а также файлы, которые он записывает заново (evidence.pdf, chain_of_custody.json, redaction_manifest.json, файлы проверки и файлы манифеста). Все остальные файлы оригинала, например images/, image_inventory.json, text_layout.json, page.mhtml, network.har и capture.warc, исключаются и перечисляются в redaction_manifest.json в dropped_for_leak_protection. Зачернение текста применяется и к этим именам файлов, потому что копия изображения называется по его URL. chain_of_custody.json сохраняет события оригинала об этих файлах с пометкой artifact_removed_in_redaction и без хешей файлов, которых нет в пакете с зачернёнными данными. rule_902_certification.pdf оригинала тоже удаляется, потому что в нём перечислены файлы и хеши оригинала; удаление указывается в redaction_manifest.json (removed_as_stale) и в chain_of_custody.json. URL снимка никогда не зачерняется, потому что он входит в подписанный манифест: зачернение текста, который есть также в URL снимка или в конечном URL, применяется везде, кроме этого URL, текст в URL остаётся, а правило указывается в `rules_in_capture_url` и в redaction_manifest.json (leakage_check.capture_url). Зачернять можно только снимки веб-страниц (сравнение, заверение или зачернение отклоняется с ошибкой 422 REDACTION_SOURCE_UNSUPPORTED).\n\nЗачернение тестового снимка (сделанного тестовым ключом, в sandbox или аккаунтом без привязанного платёжного аккаунта) является тестовым пакетом независимо от того, каким ключом оно запрошено: с водяным знаком, `test_mode: true` в manifest.json, тестовой строкой (key_env test), которая не учитывается в оплате и не расходует месячную единицу. include_eidas для такого зачернения отклоняется с ошибкой 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nОтчёт о зачернении (evidence.pdf) формируется на языке `language` (по умолчанию на языке исходного снимка), включая основной текст, титульную страницу и тестовый баннер.\n\nПеред зачернением каждый файл, который зачернение читает или переносит (скриншот, page.html, domtextcontent.txt, metadata.json, forensic_log.json, chain_of_custody.json, text_layout.json) и который указан в исходном манифесте с SHA-256, хешируется заново (redaction_manifest.json derived_from.source_files_rehashed); при несовпадении или отсутствии файла возвращается 422 REDACTION_SOURCE_INTEGRITY_FAILED и ничего не выпускается, поэтому повреждённый или изменённый источник никогда не опечатывается повторно под новой подписью. Файлы, исключаемые из пакета с зачернёнными данными, не читаются. Синхронное зачернение, источник которого читался слишком долго, останавливается до какого-либо резервирования с ошибкой 500 CAPTURE_TIME_BUDGET_EXCEEDED.\n\nЗачернение снимка под юридическим удержанием тоже удерживается (legal_hold_source \"derived:<capture id>\"), как и каждое зачернение снимка, когда на него устанавливается удержание; снятие удержания со снимка снимает и эти удержания, а пока снимок остаётся под удержанием, снять их по отдельности нельзя (409 LEGAL_HOLD_SOURCE). captured_at строки зачернения содержит время снимка его источника; redacted_at содержит время создания зачернения.\n\nЗачернение работает по принципу отказа при сбое: если проверка после зачернения всё ещё находит зачерняемый термин где-либо в пакете за пределами URL снимка и конечного URL, пакет не выпускается, а запрос возвращает 422 REDACTION_LEAK_DETECTED (`error.details` `{ files, rule_indexes, metadata_fields, in_capture_url }`; сам термин никогда не повторяется в ответе). redaction_manifest.json фиксирует `leakage_check.fail_closed: true`.\n\nЗначения обрезаются по границам символов (символ никогда не разделяется): строки поиска текста до 2000 символов, reasons до 200, reason_summary до 500, поля custodian до 200. Reasons, reason_summary и поля custodian печатаются в сертификате зачернения и должны быть простым текстом: управляющий, невидимый символ или символ направления текста отклоняется с ошибкой 400.\n\nБюджет времени и повторные попытки: запрос возвращает ответ в пределах 29-секундного лимита API Gateway. Опечатывание выполняется в рамках бюджета времени, отсчитываемого от запроса; если квалифицированная метка времени (include_eidas) не укладывается в бюджет, она не запрашивается и пакет передаётся только с привязкой OpenTimestamps (timestamps.eidas status not_obtained, reason time_budget, не оплачивается), а запрос OpenTimestamps, который не укладывается, пропускается (chain_of_custody.json фиксирует ots_skipped с reason time_budget). Передавайте Idempotency-Key, чтобы повторная попытка (например, после ответа 504 от шлюза или сетевой ошибки) возвращала то же зачернение, а не создавала и не оплачивала второе. Повторная попытка, пока первый запрос ещё выполняется, возвращает 409 IDEMPOTENCY_IN_PROGRESS с идентификатором зачернения в `error.details.capture_id` (опрашивайте с ним GET /captures/{capture_id}). Более поздняя повторная попытка возвращает 201 с исходным телом и `Idempotent-Replay: true`. Неудавшееся зачернение освобождает свой ключ, поэтому повторная попытка с тем же ключом выполняется заново.\n\nЧастота запросов ограничивается для каждого аккаунта в окнах по 60 секунд (все ключи API аккаунта и его сеанс в панели используют одно окно). После того как запрос прошёл через ограничитель, ответы этого маршрута содержат заголовки `RateLimit-Policy` и `RateLimit` (на основе IETF draft-ietf-httpapi-ratelimit-headers), а также устаревшие `X-RateLimit-Limit`, `X-RateLimit-Remaining` и `X-RateLimit-Reset`. Ответ 429 дополнительно содержит `Retry-After`.\n\nИмпортированный пакет доказательств (source import, идентификаторы с префиксом `imp_`) зачернить нельзя: 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": "Прямоугольники в пикселях скриншота исходного снимка (x и y отсчитываются от его левого верхнего угла, w и h задают ширину и высоту). Если все четыре значения области находятся в диапазоне от 0 до 1, они считаются долями ширины и высоты скриншота. Область, целиком лежащая за пределами скриншота, ничего не закрашивает и попадает в ignored_boxes; область, выходящая за край, обрезается по краю и попадает в clipped_boxes. Если все области оказываются за пределами скриншота, а зачернение текста и метаданных ничего не меняет, запрос отклоняется с 400 REDACTION_NO_OPS до какого-либо списания."
                  },
                  "text_redactions": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "match": {
                          "oneOf": [
                            {
                              "type": "string",
                              "description": "Текст как есть.",
                              "minLength": 1
                            },
                            {
                              "type": "object",
                              "properties": {
                                "pattern": {
                                  "type": "string",
                                  "description": "Регулярное выражение длиной от 1 до 500 символов. Шаблоны с вложенными квантификаторами, например (a+)+, отклоняются с 400.",
                                  "minLength": 1,
                                  "maxLength": 500
                                },
                                "flags": {
                                  "type": "string",
                                  "description": "Любые из g, i, m, s, u, каждый не более одного раза. y (sticky) отклоняется с ошибкой 400: он нашёл бы совпадение только в одной позиции и пропустил бы остальные вхождения.",
                                  "pattern": "^[gimsu]*$"
                                }
                              },
                              "required": [
                                "pattern"
                              ]
                            }
                          ]
                        },
                        "case_insensitive": {
                          "type": "boolean",
                          "default": false,
                          "description": "Найти строку как есть без учёта регистра (для регулярного выражения используйте флаг i)."
                        },
                        "reason": {
                          "type": "string",
                          "maxLength": 200,
                          "description": "Простой текст, печатается на сертификате зачернения."
                        }
                      },
                      "required": [
                        "match"
                      ]
                    },
                    "maxItems": 200
                  },
                  "metadata_fields": {
                    "type": "array",
                    "description": "Пути к полям в metadata.json (сегмент `*` соответствует каждому элементу массива) или имена `ip_dns`, `cookie_names` и `authorization` (см. выше).",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "Путь к полю в metadata.json."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "field": {
                              "type": "string"
                            },
                            "reason": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "field"
                          ]
                        }
                      ]
                    },
                    "maxItems": 100
                  },
                  "reason_summary": {
                    "type": "string",
                    "maxLength": 500,
                    "description": "Простой текст, печатается на сертификате зачернения."
                  },
                  "custodian": {
                    "type": "object",
                    "properties": {
                      "name": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string"
                      }
                    },
                    "description": "Печатается в отчёте о зачернении. name и title подчиняются правилу простого текста (см. PlainText)."
                  },
                  "language": {
                    "type": "string",
                    "description": "Язык PDF-документов для чтения человеком. Одно из значений: 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. Регистр, дефис и регион допускаются (\"DE\", \"fr-FR\" и \"PT-BR\" принимаются); просто \"pt\" и \"zh\" отклоняются. Неподдерживаемое значение возвращает 400 MALFORMED_REQUEST. Необязательный параметр: если он не указан (или пуст), отчёт формируется на языке исходного снимка."
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Как в CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "https URL, на который отправляется redaction.completed. Событие отправляется сразу после ответа 201, а не до него. URL должен использовать https и не должен указывать на частный, loopback, link-local, carrier-grade NAT адрес или адрес облачных метаданных (те же правила, что и для URL снимка; иначе 422 SSRF_BLOCKED). Пустое значение или значение только из пробелов означает отсутствие вебхука. Нужен ключ API: при отправке из сеанса в панели запрос отклоняется с 400 WEBHOOK_REQUIRES_API_KEY, потому что вебхуки подписываются секретом вебхука ключа.",
                    "format": "uri"
                  },
                  "rule_commitment_salt": {
                    "type": "string",
                    "description": "Необязательно. Ваша собственная 32-байтовая соль для криптографических обязательств (commitments) по правилам (redaction_manifest.json rule_commitments) в виде 64 шестнадцатеричных цифр или base64 / base64url; соль из одного повторяющегося байта отклоняется (400). Передайте её, чтобы иметь соль до вызова: без неё сервер создаёт соль сам и возвращает её только в ответе 201, поэтому при ответе, потерянном из-за тайм-аута (504 от шлюза, хотя зачернение всё же завершилось), соль теряется, а повтор по Idempotency-Key её не содержит. ProofSnap её не хранит."
                  }
                },
                "description": "Хотя бы одно из boxes, text_redactions или metadata_fields."
              },
              "example": {
                "text_redactions": [
                  {
                    "match": "jane.doe@example.com",
                    "reason": "personal data"
                  }
                ],
                "reason_summary": "Personal data of a third party"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Пакет с зачернёнными данными готов. Идемпотентный повтор более раннего зачернения с тем же Idempotency-Key также возвращает 201 с `Idempotent-Replay: true`: исходное тело ответа с текущим статусом и заново созданной предподписанной ссылкой package_url (отсутствует, если для пакета не удаётся создать предподписанную ссылку; в этом случае получите её через 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": "Количество применённых действий зачернения."
                    },
                    "original_signature_verified": {
                      "type": "boolean",
                      "description": "Была ли успешно проверена исходная подпись манифеста перед зачернением."
                    },
                    "leakage_warnings": {
                      "type": "integer",
                      "description": "Всегда 0: зачернение, после которого проверка находит сохранившийся термин, отклоняется с ошибкой 422 REDACTION_LEAK_DETECTED и не выпускается. Сохраняется для совместимости."
                    },
                    "package_url": {
                      "type": "string",
                      "format": "uri"
                    },
                    "package_url_expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "verify_url": {
                      "type": "string",
                      "description": "Ссылка на публичный сервис проверки ProofSnap.",
                      "format": "uri"
                    },
                    "ignored_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Индексы (с нуля) в массиве boxes запроса для областей, которые целиком лежали за пределами скриншота и ничего не закрасили. Также записываются в redaction_manifest.json (screenshot_boxes)."
                    },
                    "clipped_boxes": {
                      "type": "array",
                      "items": {
                        "type": "integer",
                        "minimum": 0
                      },
                      "description": "Индексы (с нуля) в массиве boxes запроса для областей, которые выходили за край скриншота и были по нему обрезаны. Также записываются в redaction_manifest.json (screenshot_boxes)."
                    },
                    "screenshot_method": {
                      "type": "string",
                      "nullable": true,
                      "enum": [
                        "jpeg_dct_blocks",
                        "decode_repaint_jpeg",
                        null
                      ],
                      "description": "Как были закрашены области: jpeg_dct_blocks (сжатые блоки JPEG под каждой областью заменены чёрными блоками, все остальные блоки не изменены) или decode_repaint_jpeg (скриншот декодирован, закрашен и перекодирован). null, если ни одна область ничего не закрасила. Также записывается в 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": "Фактически зачернённые области в пикселях скриншота, по одной на каждую применённую область, в порядке областей запроса без проигнорированных. При jpeg_dct_blocks каждая из них является областью, расширенной до целых блоков, поэтому содержит исходную область; при decode_repaint_jpeg это область, обрезанная по границам скриншота и расширенная до целых блоков 8 x 8 нового файла, которые зачерняются после повторного кодирования, чтобы сжатие не перенесло в область соседние цвета. Также записываются в redaction_manifest.json (screenshot_boxes.painted_rects)."
                    },
                    "text_rule_rects": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Отдельные участки скриншота, зачернённые только по текстовым правилам, без участков, на которые приходится область (участок, совпавший с двумя правилами, считается один раз): в текстовых блоках text_layout.json исходного снимка, текст которых совпадает с правилом, учитываются предполагаемые строки, содержащие совпадение, или весь блок, если оценка ненадёжна. Перечислены в redaction_manifest.json (screenshot_boxes.from_text_rules)."
                    },
                    "text_occurrences_not_located": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Вхождения текстовых зачернений в тексте страницы, которые не удалось найти на скриншоте и которые там не были закрашены (текст, которого нет в text_layout.json источника, например текст внутри изображения; блок, слишком большой для закрашивания по одному термину; области сверх лимита одного зачернения; скриншот, на котором не удалось выполнить закрашивание). Вхождения в блоке, полностью находящемся за пределами скриншота (там нечего закрашивать), здесь не учитываются; redaction_manifest.json перечисляет их как occurrences_not_on_screenshot. Это число никогда не превышает числа вхождений в тексте страницы. Проверьте скриншот и добавьте области. В redaction_manifest.json (leakage_check.screenshot_text_advisory) приведены значения по каждому правилу."
                    },
                    "text_variants_remaining": {
                      "type": "object",
                      "description": "Справочная информация, никогда не ошибка: для каждого правила с буквальным текстом указываются вхождения, оставшиеся в domtextcontent.txt и в тексте page.html этого пакета, которые правило не нашло, но которые совпадают без учёта регистра и диакритических знаков (например, «boskovické» для чувствительного к регистру правила «Boskovic»), а также есть ли термин в URL снимка или в конечном URL, которые никогда не зачерняются. `total` суммирует по каждому правилу большее из двух значений (оба файла содержат один и тот же текст страницы). Только количество, никогда не сам текст. Правила с регулярными выражениями не учитываются. Также записывается в redaction_manifest.json (leakage_check.text_variants_advisory). Присутствует, если в запросе есть зачернение текста.",
                      "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": "Присутствует, если в запросе есть зачернение текста. Индексы (с нуля) текстовых правил, текст которых также содержится в URL снимка или в конечном URL. Эти URL никогда не изменяются: текст удалён везде, кроме них, и там остаётся видимым. Пусто, если ни одно правило с ними не совпадает. Также указывается в redaction_manifest.json (leakage_check.capture_url, `remains_in_capture_url: true` для каждого правила) и в отчёте о зачернении."
                    },
                    "rule_commitment_salt": {
                      "type": "string",
                      "pattern": "^[0-9a-f]{64}$",
                      "description": "Присутствует, если в запросе есть зачернение текста. 32-байтовая соль (hex; rule_commitment_salt из запроса, если она была передана) для rule_commitments в redaction_manifest.json: для каждого текстового правила SHA-256 от соли правила, за которой следуют байты UTF-8 канонического JSON правила {\"match\": <текст или {\"pattern\", \"flags\"}>, \"case_insensitive\": <bool>}, где соль правила вычисляется как HMAC-SHA256 с этой солью в качестве ключа от текста UTF-8 \"rule:<rule_index>\" (так одно правило можно показать, раскрыв только его соль, без остальных). Правила не хранятся, а соль возвращается только в этом ответе: её нет при повторе по Idempotency-Key, в вебхуке, журнале событий или GET /captures/{capture_id}, и ProofSnap её не сохраняет. Храните её вместе со своими записями: с ней вы сможете показать, какие правила применило зачернение."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason; например, reason не в виде простого текста или флаг регулярного выражения y), REDACTION_NO_OPS (ничего не передано или ничего не найдено) или INVALID_URL (webhook_url не https). WEBHOOK_REQUIRES_API_KEY: webhook_url отправлен из сеанса в панели (для вебхуков нужен ключ 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 или 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: снимок вам не принадлежит. EIDAS_REQUIRES_LIVE_KEY: include_eidas с тестовым ключом или для зачернения тестового снимка (тогда с details.reason). EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён. PLAN_ARCHIVE_ONLY: тариф только сохраняет архив, новые задачи на нём не создаются. PLAN_API_READ_ONLY: ключ API аккаунта Website Archive (его ключ только для чтения; используйте панель). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит адрес, не являющийся адресом панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности).",
            "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: исходный снимок ещё не завершён. IDEMPOTENCY_IN_PROGRESS: запрос с тем же Idempotency-Key ещё выполняется (details.capture_id содержит ID зачернения, которое нужно опрашивать через GET /captures/{capture_id}). COMPANY_READ_ONLY: исходный снимок принадлежит компании, сохранённой как архив компании.",
            "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: данные изображения скриншота не читаются или скриншот превышает лимит для зачернения областей (150 мегапикселей для JPEG в том виде, в котором его записывает снимок, 60 мегапикселей для PNG или другого JPEG); зачернение текста и метаданных работает и без областей. REDACTION_LEAK_DETECTED: зачерняемый фрагмент сохранился за пределами URL снимка (details { files, rule_indexes, metadata_fields, in_capture_url }; in_capture_url равно true, если текст отклонённого правила также есть в URL снимка или в конечном URL, где он никогда не учитывается). REDACTION_SOURCE_UNSUPPORTED: источник не является снимком веб-страницы (details { source } или { manifest_type }). SSRF_BLOCKED: webhook_url указывает на заблокированный адрес. REDACTION_SOURCE_INTEGRITY_FAILED: файл исходного пакета не совпадает с SHA-256, указанным в его подписанном манифесте, или указанный там файл отсутствует (details { mismatched, missing }, только имена файлов); ничего не зачернено и не списано. IDEMPOTENCY_KEY_REUSED: Idempotency-Key уже использовался с другим телом запроса (или из другого окружения ключей).",
            "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: слишком много запросов в текущем 60-секундном окне. `error.details.retry_after_seconds` дублирует Retry-After. RateLimit сообщает r=0 и число секунд до сброса окна. Также RATE_LIMITED для сеанса в панели на тарифе Website Archive: его лимит в 100 единиц за сутки по UTC, общий для одиночных и пакетных снимков (одна единица за URL), compare, redact и certify-hash (одна единица за запрос); запрос, который не укладывается в остаток, отклоняется целиком (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After и details.retry_after_seconds указывают число секунд до следующих суток по UTC, а поминутные заголовки RateLimit и X-RateLimit-* не передаются). Короткий отказ, в котором есть только details.retry_after_seconds и Retry-After: 1, означает одновременное обновление счётчика: повторите запрос. При отказе не по вине вызывающей стороны единицы возвращаются; за работу, принятую с кодом 202 и позже завершившуюся ошибкой, единица не возвращается. Это мягкое ограничение, а не граница безопасности.",
            "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: непредвиденная ошибка сервера; повторите запрос с экспоненциальной задержкой. CAPTURE_TIME_BUDGET_EXCEEDED: синхронное зачернение исчерпало отведённое время и было остановлено до выпуска какого-либо пакета; оплата не списана. Если `details.reason` равно `source_read`, большую часть времени заняло чтение исходного пакета: пакет сейчас слишком велик для зачернения, повторите попытку позже.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/EidasOrAuthUnavailable"
          }
        },
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ]
      }
    },
    "/compare": {
      "post": {
        "tags": [
          "Сравнение и зачернение"
        ],
        "operationId": "compareCaptures",
        "summary": "Сравнить два снимка",
        "description": "Сравнивает два ваших завершённых снимка (текст страницы, показанные на ней изображения, метаданные) и формирует подписанный пакет сравнения. Возвращает 202; опрашивайте GET /captures/{compare_id}, который затем содержит type, compared и diff_summary. Изображения сравниваются по перечню изображений, опечатанному в каждом исходном снимке (image_inventory.json: адрес каждого показанного изображения и SHA-256 его байтов), а не попиксельно; результаты находятся в image_changes.json и в diff_summary.image_changes. Исходный снимок, сделанный до того, как снимки стали записывать перечень изображений, тоже сравнивается, но без сравнения изображений (diff_summary.image_changes.status not_run). Учитывается как один снимок. Сопоставимость (2026-10-14): два снимка, отрисованные с разными профилями устройства или с разными настройками снимка, отклоняются с 422 COMPARE_PROFILE_MISMATCH (details.differing, details.before, details.after: только различающиеся поля, внедрённые CSS и JavaScript в виде их SHA-256), если allow_profile_mismatch не равно true, а два снимка разных страниц отклоняются с 422 COMPARE_URL_MISMATCH (details.before и details.after: url и final_url в виде origin и пути), если allow_url_mismatch не равно true. Проверка выполняется сначала по строкам двух снимков (до того как что-либо прочитано или зарезервировано), затем по опечатанным metadata.json и forensic_log.json обоих пакетов в рамках описанной ниже проверки источников; несовпадение, обнаруженное только при выполнении сравнения, завершает сравнение ошибкой с этим кодом и возвращает месячную единицу. Текст сравнивается детерминированным построчным diff (без тайм-аута): diff_summary считает строки (text_unit line). Сравнивать можно только веб-снимки (сделанные через API или по расписанию): заверение хеша, сравнение или пакет с зачернёнными данными отклоняется с 422 COMPARE_SOURCE_UNSUPPORTED (`details.capture_id`, `details.source`) до чтения какого-либо пакета и до какого-либо резервирования. Прежде чем что-либо сравнивать или резервировать, заново вычисляется SHA-256 каждого файла, указанного с SHA-256 в manifest.json каждого исходного пакета; источник с изменённым или отсутствующим файлом либо с манифестом, в котором нет ни одного файла с SHA-256, отклоняется с 422 COMPARE_SOURCE_INTEGRITY_FAILED (без сравнения, без строки, без расхода месячной единицы). Сравниваются только эти перечисленные файлы: файл в исходном пакете, который манифест не перечисляет с SHA-256 (например, добавленный text_layout.json, image_inventory.json, page.html или domtextcontent.txt), считается отсутствующим. Проверка выполняется при обработке запроса, если оба исходных пакета не больше 50 МБ и она укладывается примерно в 10 секунд; в противном случае запрос принимается, проверка повторяется при выполнении сравнения, и источник, не прошедший её, завершает сравнение ошибкой (GET /captures/{compare_id} показывает status failed с error.code COMPARE_SOURCE_INTEGRITY_FAILED) и возвращает месячную единицу. Завершённое сравнение записывает эту проверку в compare.json `sources[].source_files_reverified` и в comparison_method.json `source_verification`. /compare не отправляет вебхуки: webhook_url в теле запроса игнорируется. Опрашивайте GET /captures/{compare_id}.\n\nСравнение, в котором участвует тестовый снимок (сделанный тестовым ключом, в sandbox или аккаунтом без привязанного платёжного аккаунта), является тестовым пакетом независимо от того, каким ключом оно запрошено: с водяным знаком, `test_mode: true` в manifest.json, `test_mode: true` для этого источника в compare.json `sources[]` и тестовой строкой (key_env test), которая не тарифицируется и не расходует месячную единицу. include_eidas с тестовым источником отклоняется с 403 EIDAS_REQUIRES_LIVE_KEY (details.reason).\n\nЧастота запросов ограничивается на аккаунт в 60-секундных окнах (все ключи API аккаунта и его сеанс в панели используют одно окно). После того как запрос прошёл через ограничитель, ответы этого маршрута содержат заголовки `RateLimit-Policy` и `RateLimit` (на основе черновика IETF draft-ietf-httpapi-ratelimit-headers), а также устаревшие `X-RateLimit-Limit`, `X-RateLimit-Remaining` и `X-RateLimit-Reset`. Ответ 429 дополнительно содержит `Retry-After`.\n\nПередавайте Idempotency-Key, чтобы повторная попытка (например, после 504) вернула первый ответ, а не запустила второе, оплачиваемое выполнение (24 часа, те же правила, что для POST /captures). Ключ резервируется до проверки источников, поэтому повторная попытка, пока первый запрос ещё выполняется, возвращает 409 IDEMPOTENCY_IN_PROGRESS; запрос, отклонённый проверкой источников или квотой, освобождает свой ключ, так что повторная попытка с тем же ключом проверяется заново.\n\nИмпортированный пакет доказательств (source import, ID с префиксом `imp_`) сравнить нельзя: 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": "Более ранний снимок."
                  },
                  "capture_id_2": {
                    "type": "string",
                    "description": "Более поздний снимок. Должен отличаться от capture_id_1."
                  },
                  "language": {
                    "type": "string",
                    "description": "Язык PDF сравнения. Только точный код; для любого другого значения без предупреждения используется английский.",
                    "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": "Как в CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Добавить неподписанный шаблон заверения (rule_902_certification.pdf, см. CaptureOptions.include_rule_902) для сравнения. Недоступно для снимков, отрисованных по-разному: при allow_profile_mismatch true и несовпадении профиля или настроек возвращается 422 RULE902_NOT_AVAILABLE_FOR_MISMATCH. Сейчас отключено: шаблон заверения временно выключен, поэтому поле принимается и игнорируется (rule_902_certification.pdf не создаётся и не указывается в пакете), а ответ содержит предупреждение RULE902_DISABLED.",
                    "default": false
                  },
                  "allow_profile_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Сравнить два снимка, даже если они сделаны с разными профилями устройства (options.viewport mobile или desktop, viewport_width, viewport_height, device_scale_factor; в опечатанном пакете: размер области просмотра, коэффициент масштабирования устройства, эмуляция мобильного устройства, user agent без номеров версий) или с разными настройками снимка, влияющими на отрисовку страницы (dark_mode, block_ads, block_resources, inject_css, inject_js по SHA-256, expand_collapsed, dismiss_consent, dismiss_popups, полная страница). Без этого параметра такая пара отклоняется с 422 COMPARE_PROFILE_MISMATCH. С ним diff_summary.text_verdict и images_verdict имеют значение undetermined, changed равно null, если не различается какое-либо поле метаданных содержимого, а include_rule_902 отклоняется."
                  },
                  "allow_url_mismatch": {
                    "type": "boolean",
                    "default": false,
                    "description": "Сравнить два снимка разных страниц. Два снимка считаются снимками одной страницы, если один из адресов первого (запрошенный url, а также url и final_url, записанные в его metadata.json) совпадает с одним из адресов второго после нормализации (хост в нижнем регистре, http и https считаются одинаковыми, порт по умолчанию, фрагмент, завершающая косая черта и изменчивые параметры запроса не учитываются). Без этого параметра снимки разных страниц отклоняются с 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": "Принято.",
            "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: недопустимый JSON, отсутствующий ID, один и тот же ID дважды или allow_profile_mismatch / allow_url_mismatch, не являющееся логическим значением.",
            "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 или 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: снимок вам не принадлежит. EIDAS_REQUIRES_LIVE_KEY: include_eidas с тестовым ключом или с тестовым исходным снимком (тогда с details.reason). EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён. PLAN_ARCHIVE_ONLY: тариф только сохраняет архив, новые задачи на нём не создаются. PLAN_API_READ_ONLY: ключ API аккаунта Website Archive (его ключ только для чтения; используйте панель). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит адрес, не являющийся адресом панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности).",
            "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: оба снимка должны быть в статусе completed. COMPANY_READ_ONLY: исходный снимок принадлежит компании, сохранённой как архив компании (на его основе ничего нового не создаётся).",
            "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: файл исходного снимка не совпадает с хешем SHA-256, указанным в его подписанном манифесте, или отсутствует; сравнение не выполнено, оплата не списана. `details.capture_id`, `details.mismatched`, `details.missing` указывают первый исходный снимок, не прошедший проверку; `details.reason` присутствует, если сам манифест источника использовать нельзя (например, \"the source manifest lists no file with a SHA-256\" или \"the source manifest lists no files\"); `details.sources[]` перечисляет все не прошедшие проверку источники с теми же полями. Проверьте исходный снимок (POST /captures/{capture_id}/verify). COMPARE_SOURCE_UNSUPPORTED: источник не является веб-снимком (это заверение хеша, сравнение или пакет с зачернёнными данными); ничего не прочитано, не сравнено и не списано. `details.capture_id` указывает этот источник, а `details.source` его тип, если он записан в строке. Сравнивайте исходные веб-снимки. COMPARE_PROFILE_MISMATCH: снимки сделаны с разными профилями устройства или разными настройками снимка; `details.differing` перечисляет имена полей (уровень строки: 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; уровень опечатанного пакета: viewport_width, viewport_height, device_scale_factor, is_mobile, user_agent и те же настройки), а `details.before` и `details.after` их значения. Передайте allow_profile_mismatch: true, чтобы всё равно выполнить сравнение. COMPARE_URL_MISMATCH: снимки сделаны с разных страниц; `details.before` и `details.after` содержат url и final_url в виде источника (origin) и пути. Передайте allow_url_mismatch: true, чтобы всё равно выполнить сравнение. RULE902_NOT_AVAILABLE_FOR_MISMATCH: include_rule_902 передан вместе с allow_profile_mismatch для снимков, отрисованных по-разному; `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: слишком много запросов в текущем 60-секундном окне. `error.details.retry_after_seconds` дублирует Retry-After. RateLimit сообщает r=0 и число секунд до сброса окна. Также RATE_LIMITED для сеанса в панели на тарифе Website Archive: его лимит в 100 единиц за сутки по UTC, общий для одиночных и пакетных снимков (одна единица за URL), compare, redact и certify-hash (одна единица за запрос); запрос, который не укладывается в остаток, отклоняется целиком (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After и details.retry_after_seconds указывают число секунд до следующих суток по UTC, а поминутные заголовки RateLimit и X-RateLimit-* не передаются). Короткий отказ, в котором есть только details.retry_after_seconds и Retry-After: 1, означает одновременное обновление счётчика: повторите запрос. При отказе не по вине вызывающей стороны единицы возвращаются; за работу, принятую с кодом 202 и позже завершившуюся ошибкой, единица не возвращается. Это мягкое ограничение, а не граница безопасности.",
            "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": [
          "Заверение хеша"
        ],
        "operationId": "certifyHash",
        "summary": "Заверить хеши файлов",
        "description": "Передайте один SHA-256 (hash) или до 100 значений (hashes). Пакет содержит manifest.json (с вашими хешами), manifest.sig, publickey.pem, evidence.pdf, скрипты verification/ и, при наличии, manifest.json.ots.\n\n- Один хеш: синхронно, возвращает 200 с готовым сертификатом.\n- Несколько хешей с combined: true: один сертификат со списком всех хешей, синхронно, возвращает 200.\n- Несколько хешей без combined: отдельный независимый сертификат для каждого хеша, создание запускается в фоне; возвращает 202 с certify_ids. Опрашивайте GET /captures/{certify_id}; при указании webhook_url по каждому сертификату отправляется событие certify.completed.\n\nКаждый сертификат считается одним снимком для учёта использования, месячного лимита и ограничения частоты запросов (N хешей без combined считаются как N; если N больше лимита на одно окно, возвращается 400 RATE_LIMIT_COST_TOO_HIGH). Значение `label` печатается на сертификате: оно обрезается до 200 символов и должно быть простым текстом (без управляющих и невидимых символов и символов направления текста, иначе 400). В запросе с несколькими хешами ошибка, относящаяся к одному элементу, указывает его номер в `error.details.index`.\n\nОграничение частоты запросов действует на уровне аккаунта в окнах по 60 секунд (все ключи API аккаунта и его сеанс в панели используют одно окно). После того как запрос прошёл через ограничитель, ответы этого маршрута содержат заголовки `RateLimit-Policy` и `RateLimit` (на основе черновика IETF draft-ietf-httpapi-ratelimit-headers), а также устаревшие `X-RateLimit-Limit`, `X-RateLimit-Remaining` и `X-RateLimit-Reset`. Ответ 429 дополнительно содержит `Retry-After`.\n\nПередавайте Idempotency-Key, чтобы повторный запрос (например, после 504) вернул первый ответ, а не запустил второе, оплачиваемое выполнение (24 часа, те же правила, что и для POST /captures).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "hash": {
                    "type": "string",
                    "description": "SHA-256 в виде 64 шестнадцатеричных символов (без учёта регистра). Используйте hash или hashes."
                  },
                  "label": {
                    "type": "string",
                    "description": "Отображаемое имя файла. Более длинные значения сокращаются до 200 символов."
                  },
                  "hashes": {
                    "type": "array",
                    "items": {
                      "oneOf": [
                        {
                          "type": "string",
                          "description": "SHA-256 в шестнадцатеричном виде."
                        },
                        {
                          "type": "object",
                          "properties": {
                            "hash": {
                              "type": "string"
                            },
                            "label": {
                              "type": "string"
                            }
                          },
                          "required": [
                            "hash"
                          ]
                        }
                      ]
                    },
                    "minItems": 1,
                    "maxItems": 100
                  },
                  "combined": {
                    "type": "boolean",
                    "description": "Заверить все хеши одним сертификатом.",
                    "default": false
                  },
                  "algorithm": {
                    "type": "string",
                    "enum": [
                      "sha256"
                    ],
                    "default": "sha256"
                  },
                  "language": {
                    "type": "string",
                    "description": "Язык PDF-документов для чтения человеком. Одно из значений: 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. Регистр, дефис и регион допускаются (\"DE\", \"fr-FR\" и \"PT-BR\" принимаются); просто \"pt\" и \"zh\" отклоняются. Для неподдерживаемого значения возвращается 400 MALFORMED_REQUEST."
                  },
                  "case_reference": {
                    "type": "string",
                    "description": "Печатается на сертификате. Подчиняется правилу простого текста (см. PlainText); не более 200 символов.",
                    "maxLength": 200
                  },
                  "include_eidas": {
                    "type": "boolean",
                    "description": "Как в CaptureOptions.include_eidas.",
                    "default": false
                  },
                  "include_rule_902": {
                    "type": "boolean",
                    "description": "Добавить неподписанный шаблон заверения (rule_902_certification.pdf, см. CaptureOptions.include_rule_902). Сейчас отключено: шаблон заверения временно выключен, поэтому поле принимается и игнорируется (rule_902_certification.pdf не создаётся и не указывается в пакете), а ответ содержит предупреждение RULE902_DISABLED.",
                    "default": false
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "https URL, на который отправляется certify.completed. URL должен использовать https и не должен указывать на частный, loopback, link-local, carrier-grade NAT адрес или адрес облачных метаданных (те же правила, что и для URL снимка; иначе 422 SSRF_BLOCKED). Пустое значение или значение только из пробелов означает отсутствие вебхука. Нужен ключ API: при отправке из сеанса в панели запрос отклоняется с 400 WEBHOOK_REQUIRES_API_KEY, потому что вебхуки подписываются секретом вебхука ключа.",
                    "format": "uri"
                  }
                }
              },
              "example": {
                "hash": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
                "label": "contract.pdf"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Сертификат готов (на один хеш или сводный).",
            "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": "Запущено несколько независимых сертификатов. Сертификат, который при выполнении отклоняется месячным лимитом или лимитом sandbox, завершается сбоем: его строка (GET /captures/{certify_id}) имеет status failed и 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": "Идентификаторы созданных сертификатов (включая те, запуск которых не удался)."
                    },
                    "status": {
                      "type": "string",
                      "enum": [
                        "processing"
                      ]
                    },
                    "certificates": {
                      "type": "array",
                      "description": "Одна запись на каждый переданный хеш, по порядку. status failed: этот сертификат не удалось запустить (его строка, если она была создана, помечается как failed); остальные продолжают выполняться.",
                      "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": "Сколько записей из certificates не удалось запустить. Если не удалось запустить ни одну, вместо этого возвращается 500 CONFIGURATION_ERROR."
                    },
                    "warnings": {
                      "$ref": "#/components/schemas/Rule902DisabledWarnings"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (details.reason называет поле, например \"label must be a string\"; details.index указывает элемент пакетного запроса), INVALID_HASH (details.index в пакетном запросе) или INVALID_URL (webhook_url не https или содержит учётные данные). WEBHOOK_REQUIRES_API_KEY: webhook_url отправлен из сеанса в панели (для вебхуков нужен ключ API). RATE_LIMIT_COST_TOO_HIGH: хешей (без combined) больше, чем допускает одно окно ограничения частоты запросов; 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 или QUOTA_EXCEEDED для синхронных путей.",
            "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\", если рабочий ключ или сеанс работает как тестовый, потому что не привязан платёжный аккаунт). EMAIL_NOT_VERIFIED: сеанс в панели с неподтверждённым адресом электронной почты или ключ API такого аккаунта. PLAN_ARCHIVE_ONLY: тариф только сохраняет архив, новые задачи на нём не создаются. PLAN_API_READ_ONLY: ключ API аккаунта на тарифе Website Archive (его ключ только для чтения; используйте панель). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит адрес, не принадлежащий панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности).",
            "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 указывает на частный адрес, адрес loopback, link-local или иной заблокированный адрес.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: слишком много запросов в текущем 60-секундном окне. `error.details.retry_after_seconds` дублирует Retry-After. RateLimit сообщает r=0 и число секунд до сброса окна. Также RATE_LIMITED для сеанса в панели на тарифе Website Archive: его лимит в 100 единиц за сутки по UTC, общий для одиночных и пакетных снимков (одна единица за URL), compare, redact и certify-hash (одна единица за запрос); запрос, который не укладывается в остаток, отклоняется целиком (details.daily_limit, details.remaining, details.action \"wa_session_captures\"; Retry-After и details.retry_after_seconds указывают число секунд до следующих суток по UTC, а поминутные заголовки RateLimit и X-RateLimit-* не передаются). Короткий отказ, в котором есть только details.retry_after_seconds и Retry-After: 1, означает одновременное обновление счётчика: повторите запрос. При отказе не по вине вызывающей стороны единицы возвращаются; за работу, принятую с кодом 202 и позже завершившуюся ошибкой, единица не возвращается. Это мягкое ограничение, а не граница безопасности.",
            "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: заверение не удалось (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: синхронное заверение (одного хеша или сводное) не завершилось примерно за 25 с. Оно ещё может завершиться: details.certify_id и details.poll_path указывают запись, которую нужно опрашивать через 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": [
          "Вебхуки"
        ],
        "operationId": "listWebhookEvents",
        "summary": "Журнал доставки вебхуков",
        "description": "Все попытки доставки для одного снимка, сертификата или зачернения, от самых старых к новым.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Журнал доставок.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "capture_id": {
                      "type": "string"
                    },
                    "webhook_url": {
                      "type": "string",
                      "description": "URL вебхука записи (options.webhook_url снимка или URL, указанный с сертификатом или зачернением; null, если его нет). Для владельца компании, для которой сделан снимок, при чтении журнала участника компании показываются только схема, хост, порт и путь (запрос и фрагмент удаляются, так как могут содержать токен); это чтение сохраняется в истории доступа к снимку (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, если доставка выполнена. Иначе общий класс ошибки, никогда не исходная сетевая ошибка: connection_failed, timeout, blocked_address (в момент доставки хост разрешился в частный или иным образом заблокированный адрес), redirect_not_followed (эндпоинт ответил 3xx; перенаправления никогда не выполняются), http_<status> (ответ вне диапазона 2xx) или фраза с объяснением, почему событие не было отправлено (нет ключа API, ключ отозван или не найден, нет секрета вебхука, не удалось получить секрет)."
                          },
                          "delivery_id": {
                            "type": "string",
                            "description": "Заголовок webhook-id этой доставки без префикса `msg_`."
                          }
                        }
                      }
                    },
                    "attempt_count": {
                      "type": "integer"
                    },
                    "last_success": {
                      "type": "string",
                      "description": "attempted_at последней успешной попытки.",
                      "nullable": true
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: снимок вам не принадлежит.",
            "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": [
          "Снимки по расписанию"
        ],
        "operationId": "createSchedule",
        "summary": "Создать расписание",
        "description": "interval и cron: каждый запуск делает снимок и, если задан webhook_url, отправляет capture.completed или capture.failed.\n\non_change: каждый запуск сначала загружает страницу (обычным HTTP-запросом или с рендерингом в браузере при js_render) и вычисляет хеш её нормализованного текста. Эта проверка не является снимком и не оплачивается. Первый запуск делает базовый снимок. Когда хеш меняется, делается новый снимок, он сравнивается с предыдущим, и на webhook_url отправляется monitor.change_detected. Страница, которая возвращает ошибку или почти не содержит текста, считается неизменившейся.\n\nПосле 5 неудачных запусков подряд расписание приостанавливается автоматически. Если достигнут месячный лимит или по подписке есть просроченный платёж, запуски пропускаются, а расписание остаётся активным.\n\nОграничения по тарифам:\n\n| Тариф | Активные расписания | Минимальная периодичность | on_change |\n| --- | --- | --- | --- |\n| sandbox | 0 | недоступно | нет |\n| developer | 3 | 300 с | да |\n| startup | 25 | 300 с | да |\n| growth | 200 | 300 с | да |\n| custom | без ограничений | 300 с | да |\n| legal_solo | 3 | 300 с | да |\n| legal_seats | 3 | 300 с | да |\n| website_archive | 30 | 86400 с | да |\n| archive_only | 0 | недоступно | нет |\n\nIdempotency-Key: повторный запрос с тем же ключом и телом в течение 24 часов получает первый ответ 201 (заголовок Idempotent-Replay: true) вместо создания второго расписания; тот же ключ с другим телом даёт 422 IDEMPOTENCY_KEY_REUSED.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ScheduleRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Создано.",
            "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) или ошибка параметров снимка. WEBHOOK_REQUIRES_API_KEY: webhook_url отправлен из сеанса в панели (для вебхуков нужен ключ API).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED (details.max_active, details.active) или ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён. PLAN_ARCHIVE_ONLY: тариф сохраняет только архив и не позволяет создавать новые задачи. PLAN_API_READ_ONLY: ключ API аккаунта Website Archive (его ключ только для чтения; используйте панель). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит в нём адрес, не являющийся адресом панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "SSRF_BLOCKED: url или webhook_url указывает на частный адрес, адрес loopback, link-local или иной заблокированный адрес.",
            "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": [
          "Снимки по расписанию"
        ],
        "operationId": "listSchedules",
        "summary": "Список расписаний",
        "description": "До 100 ваших расписаний.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Расписания.",
            "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": [
          "Снимки по расписанию"
        ],
        "operationId": "getSchedule",
        "summary": "Получить расписание",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Расписание.",
            "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": [
          "Снимки по расписанию"
        ],
        "operationId": "updateSchedule",
        "summary": "Приостановить, возобновить или изменить расписание",
        "description": "Передайте status, чтобы приостановить или возобновить расписание, и/или любое поле ScheduleRequest, чтобы изменить его. Переданное поле верхнего уровня заменяет сохранённое, включая явный null (так очищаются webhook_url, timezone и js_render); поле, которое вы не передали, сохраняет своё значение; результат заново проверяется целиком. `options` составляет исключение из пополевого редактирования: этот объект заменяется целиком, а не объединяется по ключам, поэтому передавайте полный набор нужных options (сначала прочитайте их через GET). Опция, не включённая в объект, удаляется. При возобновлении проверяется лимит активных расписаний, сбрасывается счётчик сбоев, а следующий запуск рассчитывается от текущего момента.\n\nИз сеанса в панели задать webhook_url нельзя (400 WEBHOOK_REQUIRES_API_KEY); передача null или пустой строки очищает его. У расписания, созданного из сеанса в панели, никогда не было ключа API для подписи, поэтому при его редактировании также удаляется ранее сохранённый в нём webhook_url, а ключ API тоже не может его добавить (400 WEBHOOK_REQUIRES_API_KEY): планировщик подписывает вебхуки расписания секретом вебхука того ключа, с которым было создано расписание. Чтобы получать вебхуки, создайте расписание с ключом API.\n\nИзменение url, mode или js_render у расписания on_change (или переключение расписания на on_change либо с него) сбрасывает его базовую точку (сохранённый хеш проверки и последний снимок), поэтому следующий запуск делает новый базовый снимок, а не сравнивает новую цель со старой. Тело запроса должно быть объектом JSON (иначе 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": "Часовой пояс IANA для cron. null или пустая строка сбрасывает значение на UTC."
                  },
                  "webhook_url": {
                    "type": "string",
                    "description": "Передайте null или пустую строку, чтобы удалить значение. URL должен использовать https и не должен указывать на частный адрес, адрес loopback, link-local, carrier-grade NAT или адрес метаданных облака (те же правила, что и для URL снимка; иначе 422 SSRF_BLOCKED). Ключ API нужен с обеих сторон: если значение передано из сеанса в панели или передано с ключом API для расписания, созданного в панели, запрос отклоняется с ошибкой 400 WEBHOOK_REQUIRES_API_KEY, поскольку вебхуки подписываются секретом вебхука того ключа, с которым было создано расписание.",
                    "nullable": true
                  },
                  "js_render": {
                    "type": "boolean",
                    "nullable": true,
                    "description": "null означает false."
                  },
                  "options": {
                    "allOf": [
                      {
                        "$ref": "#/components/schemas/CaptureOptions"
                      }
                    ],
                    "description": "Заменяет сохранённые параметры целиком (без объединения по ключам). Передайте полный набор; параметр, который не передан, удаляется. null сбрасывает параметры к значениям по умолчанию. options.webhook_url никогда не сохраняется (см. webhook_url)."
                  },
                  "max_capture_age_seconds": {
                    "type": "integer",
                    "nullable": true,
                    "minimum": 86400,
                    "maximum": 34560000,
                    "description": "Только on_change: полный снимок не реже чем каждые N секунд даже без изменений (от 1 до 400 дней, больше interval_seconds). null удаляет параметр. При переключении режима на interval или cron он сбрасывается."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённое расписание.",
            "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. WEBHOOK_REQUIRES_API_KEY: webhook_url отправлен из сеанса в панели или с ключом API для расписания, созданного в панели (у него нет ключа, секретом вебхука которого можно было бы подписать вебхуки).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "SCHEDULE_LIMIT_REACHED или ON_CHANGE_REQUIRES_PLAN. EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён (при возобновлении приостановленного расписания; приостановка и редактирование остаются доступны). PLAN_API_READ_ONLY: ключ API аккаунта Website Archive, при любом PATCH (включая приостановку и редактирование). PLAN_API_READ_ONLY с details.reason \"session_outside_dashboard\": сеанс в панели аккаунта Website Archive, запрос которого не содержит заголовка Origin или содержит в нём адрес, не являющийся адресом панели ProofSnap (новые задачи на этом тарифе создаются только в панели; это мягкое ограничение, а не граница безопасности). PLAN_ARCHIVE_ONLY: возобновление расписания на тарифе 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 или webhook_url указывает на частный адрес, адрес loopback, link-local или иной заблокированный адрес.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Снимки по расписанию"
        ],
        "operationId": "deleteSchedule",
        "summary": "Удалить расписание",
        "description": "Останавливает будущие запуски. Уже сделанные снимки сохраняются.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Удалено.",
            "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: ключ API аккаунта Website Archive (его ключ только для чтения; используйте панель).",
            "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": [
          "Снимки по расписанию"
        ],
        "operationId": "listScheduleRuns",
        "summary": "Список запусков расписания",
        "description": "50 последних снимков, сделанных по этому расписанию (проверки on_change, не обнаружившие изменений, не перечисляются).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Запуски.",
            "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": [
          "Дела"
        ],
        "operationId": "listMatters",
        "summary": "Список дел",
        "description": "До 200 дел, участником которых вы являетесь. Дела компании, из которой вы вышли, не перечисляются.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Дела.",
            "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: тариф не относится к тарифам Legal.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "post": {
        "tags": [
          "Дела"
        ],
        "operationId": "createMatter",
        "summary": "Создать дело",
        "description": "Вы становитесь его владельцем. Журнал цепочки хранения доказательств начинается с записи matter.created. У текстовых полей удаляются пробелы по краям, и они сокращаются до 200 символов.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). Обязательное поле. Отображается на публичной странице проверки дела."
                  },
                  "client": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). Пустое значение или null означает, что поле не задано (сохраняется как null). Значение не строкового типа отклоняется.",
                    "nullable": true
                  },
                  "case_number": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). Пустое значение или null означает, что поле не задано (сохраняется как null). Значение не строкового типа отклоняется. Отображается на публичной странице проверки дела.",
                    "nullable": true
                  },
                  "opposing_party": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). Пустое значение или null означает, что поле не задано (сохраняется как null). Значение не строкового типа отклоняется.",
                    "nullable": true
                  },
                  "jurisdiction": {
                    "type": "string",
                    "description": "Произвольный текст. Простой текст: повторяющиеся пробельные символы заменяются одним, а в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые в написании на хинди и персидском, разрешены) (иначе 400 MALFORMED_REQUEST). Пустое значение или null означает «не задано» (сохраняется как null). Значение, не являющееся строкой, отклоняется. Отображается на публичной странице проверки дела.",
                    "maxLength": 200,
                    "nullable": true
                  }
                },
                "required": [
                  "name"
                ]
              },
              "example": {
                "name": "Smith v. Jones",
                "case_number": "2026-CV-0142"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Создано.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MISSING_FIELD: name отсутствует или пусто. MALFORMED_REQUEST: недопустимый JSON. MALFORMED_REQUEST: текстовое поле не является строкой, длиннее 200 символов или содержит управляющие символы или символы направления текста.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: тариф не относится к тарифам Legal. EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "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": [
          "Дела"
        ],
        "operationId": "getMatter",
        "summary": "Получить дело",
        "description": "Дело с его снимками (до 500), журналом цепочки хранения доказательств и до 100 событий доступа. `members` содержит email каждой записи `member_uids`, а `actor_emails` сопоставляет uid, упомянутые в журнале цепочки хранения доказательств и audit_stream этого дела (участников действий и цель `member.added` / `member.removed`, то есть и удалённых участников), с их email; email равен null или uid отсутствует в сопоставлении, если его не удалось прочитать. Эти поля получают только вызывающие, которые могут открыть дело; `/matter-verify` никогда не содержит email. Вызывающий, который может отозвать публичную ссылку для проверки (владелец дела, владелец компании, подписка которой закончилась или которая хранится как архив компании), также получает `share`: `{ share_url, created_at, expires_at }` активной ссылки (`expires_at` всегда null: ссылки действуют, пока их не отзовут) или null, если ссылки нет; другие участники это поле никогда не получают. Записи audit_stream содержат source_ip и user_agent только для владельца дела; участники получают записи без этих двух полей.\n\nДело компании, подписка которой закончилась, содержит `read_only: true`.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Дело.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Matter"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "members": {
                          "type": "array",
                          "description": "Одна запись на каждую запись `member_uids`, в том же порядке.",
                          "items": {
                            "type": "object",
                            "properties": {
                              "uid": {
                                "type": "string"
                              },
                              "email": {
                                "type": "string",
                                "format": "email",
                                "nullable": true,
                                "description": "Null, если аккаунт невозможно прочитать."
                              }
                            }
                          }
                        },
                        "actor_emails": {
                          "type": "object",
                          "description": "`{ uid: email }` для людей, упомянутых в custody_log и audit_stream этого дела (кроме системного исполнителя). uid, email которого не удаётся прочитать, отсутствует.",
                          "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": "Только для вызывающего, который может отозвать ссылку. Null, если активной ссылки нет.",
                          "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": "Всегда null: срок действия публичных ссылок не истекает."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: тариф не относится к тарифам 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": [
          "Дела"
        ],
        "operationId": "updateMatter",
        "summary": "Обновить дело",
        "description": "Только для владельца. Текстовые поля обрезаются по краям и сокращаются до 200 символов. Поле name обязательно: если оно передано, это должна быть непустая строка; пустое или состоящее только из пробелов name, null или значение не строкового типа отклоняется с кодом 400, и ничего не меняется. Участники: add_member_email добавляет человека, у которого уже есть аккаунт ProofSnap и который относится к компании дела; при отсутствии аккаунта, неподтверждённом адресе и аккаунте вне компании возвращается один и тот же ответ 404 NOT_FOUND; remove_member_uid удаляет участника (но не владельца). Если переданы оба поля, применяется только add_member_email. В деле не более 50 участников (заполненное дело отклоняет запрос ещё до поиска адреса). Изменения состава участников записываются в журнал цепочки хранения доказательств. Изменение name, client, case_number, opposing_party, jurisdiction или status записывается в журнал цепочки хранения доказательств как matter.updated; его target представляет собой JSON-строку с полем fields (имена изменённых полей) и, если изменился статус, новым status. Значения свободного текста не записываются в хешированный публичный журнал цепочки хранения доказательств: старые и новые значения попадают в журнал доступа (audit_stream, запись matter.updated). Для дела в архиве переименование или add_member_email отклоняется с кодом 409 MATTER_ARCHIVED, если тот же запрос не открывает его заново (status \"open\"); remove_member_uid и повторное открытие по-прежнему работают. Каждое приглашение учитывается в суточном лимите 50 на одного приглашающего владельца (приглашения в компанию из панели и add_member_email для дел считаются вместе, включая отказы, так как каждое из них ищет адрес); при достижении лимита запрос возвращает 429 RATE_LIMITED. Сбой на стороне сервера не учитывается.\n\nЮридическое удержание: `{ \"legal_hold\": true|false }` без других полей устанавливает юридическое удержание дела (только владелец дела) и в том же запросе применяет его к снимкам дела с теми же последствиями, что и удержание снимка (юридическое удержание S3 Object Lock, записи цепочки хранения доказательств capture.legal_hold_placed / capture.legal_hold_released, цепочка событий снимка). Ответ содержит дело, а также `changed` (флаг изменился), `captures_held` или `captures_released`, `skipped` ([{ capture_id, code }], например снимок в корзине) и `truncated` (более 1000 снимков: отправьте то же значение ещё раз, чтобы продолжить). Снимки, привязанные к делу позже, пока удержание действует, удерживаются в момент привязки. Одна единица ограничения частоты запросов на запрос плюс одна на каждые 10 изменённых снимков.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "minLength": 1,
                    "description": "Должно содержать хотя бы один непробельный символ. Не передавайте поле, чтобы сохранить текущее название. Действует то же правило простого текста: повторяющиеся пробелы заменяются одним, не более 200 символов, без управляющих, невидимых символов и символов направления текста (U+200C и U+200D разрешены).",
                    "maxLength": 200
                  },
                  "client": {
                    "type": "string",
                    "nullable": true,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). null или строка только из пробелов очищает поле (сохраняется как null). Значение не строкового типа отклоняется с кодом 400.",
                    "maxLength": 200
                  },
                  "case_number": {
                    "type": "string",
                    "nullable": true,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). null или строка только из пробелов очищает поле (сохраняется как null). Значение не строкового типа отклоняется с кодом 400.",
                    "maxLength": 200
                  },
                  "opposing_party": {
                    "type": "string",
                    "nullable": true,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). null или строка только из пробелов очищает поле (сохраняется как null). Значение не строкового типа отклоняется с кодом 400.",
                    "maxLength": 200
                  },
                  "jurisdiction": {
                    "type": "string",
                    "nullable": true,
                    "description": "Простой текст: повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, не более 200 символов, без управляющих символов, символов направления текста (bidi) и невидимых символов (соединители U+200C и U+200D, необходимые для правописания хинди и персидского языка, допускаются) (иначе 400 MALFORMED_REQUEST). null или строка только из пробелов очищает поле (сохраняется как null). Значение не строкового типа отклоняется с кодом 400.",
                    "maxLength": 200
                  },
                  "status": {
                    "type": "string",
                    "description": "Чтобы опечатать дело, используйте 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": "Юридическое удержание дела, передаётся отдельно (вместе с другим полем: 400). true удерживает каждый текущий снимок дела и каждый снимок, назначенный делу, пока действует удержание (legal_hold_source \"matter:<id>\"; снимок, который уже удерживается, сохраняет собственное удержание). false снимает только удержания, установленные делом; ручное или массовое удержание остаётся. Только для владельца дела. Просроченный платёж по подписке не блокирует операцию. Для установки нужен платный тариф (403 LEGAL_HOLD_NOT_AVAILABLE, details.reason plan), а снимки тестового режима пропускаются (код LEGAL_HOLD_NOT_AVAILABLE в `skipped`); одиночное false принимается и после окончания тарифа Legal."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Обновлённое дело.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matter"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: name пустое, состоит только из пробелов или не является строкой; текстовое поле не является строкой, длиннее 200 символов или содержит управляющие символы или символы направления текста; недопустимый status; нечего обновлять; пользователь уже является участником; или попытка удалить владельца.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (не владелец), LEGAL_HOLD_NOT_AVAILABLE (legal_hold true без платного тарифа) или EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND или NOT_FOUND (для этого адреса электронной почты нет подходящего аккаунта: аккаунта нет, он не подтверждён или не входит в компанию, ответ во всех случаях одинаковый; или remove_member_uid не является участником).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "MATTER_SEALED или MATTER_ARCHIVED (переименование или новый участник в деле в архиве). COMPANY_READ_ONLY: дело принадлежит компании, подписка которой закончилась или которая хранится как архив компании (вызывающий на тарифе Legal; открытие, экспорт и отзыв публичной ссылки по-прежнему работают).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: достигнут суточный лимит в 50 приглашений участников на одного приглашающего владельца (приглашения в компанию из панели и add_member_email для дела учитываются вместе; details.daily_limit, details.action \"member_invite\"). Retry-After указывает, когда закончатся сутки по 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": [
          "Дела"
        ],
        "operationId": "assignMatterCaptures",
        "summary": "Добавить или удалить снимки",
        "description": "Вызвать может любой участник, но перемещаются только снимки, созданные вызывающим; остальные возвращаются в skipped, как и снимки, уже находящиеся в другом деле, и снимки, уже находящиеся в этом деле (или, при unassign, отсутствующие в нём). Элементы, не являющиеся идентификаторами снимков, игнорируются, обрабатывается не более 100. Каждое изменение записывается в журнал цепочки хранения доказательств. Дело в архиве не принимает новые снимки (409 MATTER_ARCHIVED); unassign по-прежнему работает.",
        "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 удаляет снимки из дела.",
                    "default": false
                  }
                },
                "required": [
                  "capture_ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Результат.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "assigned": {
                      "type": "integer",
                      "description": "Количество изменённых снимков."
                    },
                    "skipped": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    },
                    "unassign": {
                      "type": "boolean"
                    },
                    "legal_holds_placed": {
                      "type": "integer",
                      "description": "Присутствует, если для дела действует юридическое удержание: сколько из привязанных снимков оно поставило на удержание."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: нет ни одного допустимого ID снимка.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED или EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "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 или MATTER_ARCHIVED (добавление в дело в архиве). COMPANY_READ_ONLY: дело принадлежит компании, подписка которой закончилась или которая хранится как архив компании (вызывающий на тарифе Legal; открытие, экспорт и отзыв публичной ссылки по-прежнему работают).",
            "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": [
          "Дела"
        ],
        "operationId": "sealMatter",
        "summary": "Опечатать дело",
        "description": "Только для владельца. Привязывает текущий головной хеш цепочки хранения доказательств с помощью OpenTimestamps, а если в развёртывании настроен eIDAS и вызывающий работает в рабочем режиме (рабочий ключ API или сеанс в панели с привязанной оплатой), то и с помощью метки времени eIDAS (метка времени eIDAS при опечатывании не оплачивается; если получить её не удалось, опечатывание выполняется без неё). Для вызывающего в тестовом режиме (тестовый ключ API или сеанс sandbox) дело опечатывается только с OpenTimestamps, и при опечатывании записывается eidas_status not_obtained с eidas_reason test_key; у запроса нет параметра eIDAS, поэтому это не ошибка. После этого дело доступно только для чтения: никаких правок, никаких изменений снимков, и его снимки нельзя удалить. Дело, в котором ещё есть снимок в корзине, не опечатывается (409 MATTER_HAS_TRASHED_CAPTURES): сначала восстановите снимок или уберите его из дела.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Опечатано.",
            "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: у дела нет головного хеша цепочки хранения доказательств.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: тариф не относится к тарифам Legal. FORBIDDEN: не владелец. EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "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 (уже опечатано), MATTER_CHANGED (журнал цепочки хранения доказательств изменился во время опечатывания; повторите попытку) или MATTER_HAS_TRASHED_CAPTURES (снимок дела находится в корзине; сначала восстановите его или уберите из дела). COMPANY_READ_ONLY: дело принадлежит компании, подписка которой закончилась или которая хранится как архив компании (вызывающий на тарифе Legal; открытие, экспорт и отзыв публичной ссылки по-прежнему работают).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "TIMESTAMP_SERVICE_UNAVAILABLE (привязка не удалась, ничего не изменено; повторите запрос) или CONFIGURATION_ERROR.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "EIDAS_SEAL_RETRY: метка времени eIDAS для опечатывания не пришла вовремя (или ещё выпускается). Ничего не опечатано. Повторите запрос через Retry-After секунд: повторный запрос получит метку времени, уже выпущенную для этого головного хеша цепочки хранения доказательств, второй метки не будет. SERVICE_UNAVAILABLE: шлюз не смог проверить учётные данные; повторите запрос с увеличивающейся задержкой.",
            "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": [
          "Дела"
        ],
        "operationId": "exportMatter",
        "summary": "Экспортировать дело",
        "description": "Только для владельца. Собирает один ZIP с custody_report.pdf, custody_log.json, matter.json, README.txt, access_log.json (если есть события доступа) и пакетом доказательств каждого снимка. При включённой нумерации Bates (по умолчанию) добавляются копии каждого evidence.pdf со штампом Bates в папке exhibits/ (приложения нумеруются начиная с самого старого доказательства), а с formats.loadfile также файл загрузки Concordance/Opticon. С formats.pdfa копии для подачи также преобразуются в PDF/A, если доступен конвертер (при синхронном экспорте только в пределах бюджета времени); PDF/A являются только копии, учтённые в pdfa_count, остальные являются PDF без заявления о соответствии PDF/A, а README.txt упоминает PDF/A, только если pdfa_count больше 0.\n\nДело, в котором не более 20 снимков, экспортируется синхронно (200). Более крупное дело экспортируется в фоновом режиме: ответ 202 с job_id; опрашивайте GET /matters/{matter_id}/export?job_id=. Снимки, не укладывающиеся в ограничение размера (120 МБ при синхронном экспорте; 800 МБ и 300 снимков в фоновом режиме), перечисляются в отчётах, но не включаются в архив.\n\nРаботает с открытыми и опечатанными делами. Дело в архиве отклоняется с 409 MATTER_ARCHIVED (сначала откройте его снова), кроме случая, когда вызывающий не может открыть его снова: владелец компании, подписка которой закончилась (пока не истёк срок хранения её данных), и аккаунт Archive only. Каждый экспорт записывается в журнал доступа как export.generated. Каждый экспорт учитывается в суточном лимите 50 на пользователя; фоновый экспорт, завершившийся ошибкой, возвращает свою единицу.",
        "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": "Только буквы и цифры, в верхнем регистре, не более 12. По умолчанию: из номера дела или названия дела."
                      },
                      "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": "Включить файл загрузки для e-discovery (требуется нумерация Bates).",
                        "default": true
                      },
                      "pdfa": {
                        "type": "boolean",
                        "description": "Преобразовать копии для подачи с нумерацией Bates в PDF/A (требуется нумерация Bates). По возможности: непреобразованная копия остаётся PDF без заявления о соответствии PDF/A; pdfa_count в ответе показывает, сколько копий преобразовано.",
                        "default": true
                      }
                    }
                  },
                  "export_locale": {
                    "type": "string",
                    "description": "Язык custody_report.pdf, указателя приложений и 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 (вместо подчёркивания допускается дефис). Для других значений используется английский."
                  },
                  "declaration": {
                    "type": "object",
                    "description": "Принимается для совместимости. Его текстовые поля (name, title, party, case_caption, executed_location) подчиняются правилу простого текста (см. PlainText): значение с запрещённым символом отклоняется с ошибкой 400 MALFORMED_REQUEST. Слишком длинные значения сокращаются (name, title, executed_location до 120, party до 160, case_caption до 200 символов)."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Экспорт готов.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "matter_id": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "description": "Предподписанная ссылка для скачивания, действует 24 часа.",
                      "format": "uri"
                    },
                    "expires_at": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "capture_count": {
                      "type": "integer"
                    },
                    "captures_included": {
                      "type": "integer"
                    },
                    "captures_skipped": {
                      "type": "integer",
                      "description": "Снимки, перечисленные в отчётах, но не включённые в архив, потому что достигнуто ограничение по размеру."
                    },
                    "custody_events": {
                      "type": "integer"
                    },
                    "exhibits_count": {
                      "type": "integer",
                      "description": "Копии приложений со штампом Bates."
                    },
                    "bates_prefix": {
                      "type": "string",
                      "nullable": true
                    },
                    "searchable": {
                      "type": "boolean",
                      "description": "Всегда false: в копии для подачи текстовый слой не добавляется. Сохраняется для стабильной структуры ответа."
                    },
                    "pdfa_count": {
                      "type": "integer",
                      "description": "Копии для подачи в exhibits/ в формате PDF/A. 0 означает, что ни одна копия не в формате PDF/A и ни одна на это не претендует."
                    },
                    "loadfile": {
                      "type": "boolean"
                    },
                    "size_bytes": {
                      "type": "integer"
                    },
                    "generated_at": {
                      "type": "string",
                      "format": "date-time"
                    }
                  }
                }
              }
            }
          },
          "202": {
            "description": "Экспорт запущен в фоновом режиме.",
            "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": "Включает этап /v1."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST: недопустимый параметр экспорта (details.reason).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "description": "PAYMENT_REQUIRED: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: тариф не относится к тарифам Legal. FORBIDDEN: не владелец. EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "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: сначала откройте дело снова (PATCH status \"open\").",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "CONFIGURATION_ERROR: не удалось сформировать или запустить экспорт.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          },
          "504": {
            "description": "UPSTREAM_TIMEOUT: синхронный экспорт не завершился примерно за 25 с (details.reason). Повторите запрос.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Дела"
        ],
        "operationId": "getMatterExport",
        "summary": "Опрос фонового экспорта",
        "description": "Только для владельца. Задание, которое всё ещё обрабатывается через 30 минут после запуска, отображается как failed с ошибкой \"export timed out\". Задание завершается один раз: задание, отмеченное как failed, позже никогда не становится ready, а задание в состоянии ready никогда не отображается как failed.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Статус задания. Когда задание готово, заполняются url и счётчики.",
            "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: отсутствует job_id.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: тариф не относится к тарифам Legal. FORBIDDEN: не владелец.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND или NOT_FOUND для неизвестного задания.",
            "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": [
          "Дела"
        ],
        "operationId": "shareMatter",
        "summary": "Создать публичную ссылку для проверки",
        "description": "Только для владельца. Возвращает ссылку, по которой любой может увидеть название дела, номер дела, юрисдикцию, статус, журнал цепочки хранения доказательств и сведения об опечатывании (см. GET /matter-verify). Содержимое снимков не раскрывается. Повторный вызов возвращает ту же ссылку. Ссылка действует, пока вы её не отзовёте.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Публичная ссылка для проверки.",
            "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: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED: тариф не относится к тарифам Legal. FORBIDDEN: не владелец. EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "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: ссылкой на дело в архиве поделиться нельзя. COMPANY_READ_ONLY: дело принадлежит компании, подписка которой закончилась или которая хранится как архив компании (вызывающий на тарифе Legal; открытие, экспорт и отзыв публичной ссылки по-прежнему работают).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Дела"
        ],
        "operationId": "revokeMatterShare",
        "summary": "Отозвать публичную ссылку для проверки",
        "description": "Только для владельца. Ссылка перестаёт работать. Последующий POST создаёт новую. Владелец компании, подписка которой закончилась (или компании, сохранённой как архив компании), и владелец дела на тарифе Archive only могут отозвать ссылку без тарифа Legal (создание ссылки остаётся функцией тарифа Legal).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Отозвано.",
            "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: вызывающий в рабочем режиме, у подписки которого (или у подписки его компании) есть просроченный платёж или она не оплачена."
          },
          "403": {
            "description": "MATTERS_NOT_ENABLED, FORBIDDEN (не владелец) или EMAIL_NOT_VERIFIED: сеанс в панели, адрес электронной почты которого не подтверждён, или ключ API такого аккаунта.",
            "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": [
          "Публичный доступ"
        ],
        "operationId": "health",
        "summary": "Проверка работоспособности",
        "description": "Всегда 200, пока API отвечает. При deep=1 (или true) ответ дополнительно содержит dependencies: настроена ли каждая внешняя служба, а для eIDAS результат фактической проверки службы меток времени. Проверяется только eIDAS, по остальным сообщается лишь конфигурация. status становится degraded, если eIDAS настроен, а его проверка не удалась. Проверка, не ответившая вовремя, отражается в dependencies (probe_timeout или eidas.error timeout), но никогда не как неудачный /health. Частота запросов с deep=1 ограничена для каждого IP клиента (10 в минуту): при превышении запрос возвращает 429 RATE_LIMITED с Retry-After.",
        "security": [],
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1",
                "true"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Состояние работоспособности.",
            "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": "Миллисекунды с момента запуска обслуживающего экземпляра."
                    },
                    "version": {
                      "type": "string"
                    },
                    "dependencies": {
                      "type": "object",
                      "properties": {
                        "eidas": {
                          "type": "object",
                          "properties": {
                            "configured": {
                              "type": "boolean"
                            },
                            "healthy": {
                              "type": "boolean",
                              "description": "null, если не настроено.",
                              "nullable": true
                            },
                            "error": {
                              "type": "string",
                              "description": "Фиксированный код, если проверка eIDAS не сообщила об исправности: timeout (нет ответа в течение 15 с), unavailable (служба меток времени ответила, что неисправна), error (проверку не удалось выполнить). Подробности записываются в журнал на сервере и никогда не возвращаются.",
                              "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": "Только если не удалась сама проверка зависимостей: probe_timeout, если она не ответила в течение 20 секунд, иначе probe_failed. Ответ всё равно 200."
                        }
                      },
                      "description": "Только с deep=1."
                    }
                  }
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/ledger/{hash}": {
      "get": {
        "tags": [
          "Публичный доступ"
        ],
        "operationId": "ledgerLookup",
        "summary": "Поиск хеша манифеста",
        "description": "Проверяет, зафиксировал ли ProofSnap выпуск пакета, у которого manifest.json имеет этот хеш SHA-256. Снимки, сертификаты, сравнения и пакеты с зачернёнными данными записываются в момент выпуска с хешем того манифеста, который входит в пакет (после шага квалифицированной метки времени); пакетов, выпущенных до появления реестра, в нём нет. issued: false означает только то, что записи нет.",
        "security": [],
        "parameters": [
          {
            "name": "hash",
            "in": "path",
            "required": true,
            "description": "SHA-256 файла manifest.json: 64 шестнадцатеричных символа в любом регистре, при необходимости с префиксом sha256:.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Результат поиска.",
            "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": "Отсутствует, если issued равно false."
                    },
                    "type": {
                      "type": "string",
                      "enum": [
                        "capture",
                        "certify",
                        "compare",
                        "redaction"
                      ],
                      "nullable": true,
                      "description": "Отсутствует, если issued равно false."
                    },
                    "eidas": {
                      "type": "boolean",
                      "nullable": true,
                      "description": "Содержит ли выпущенный пакет квалифицированную метку времени eIDAS (manifest.json.tsr). Отсутствует, если issued равно false; null для записей, сделанных до 2026-10-01."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: хеш не состоит из 64 шестнадцатеричных символов.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matter-verify": {
      "get": {
        "tags": [
          "Публичный доступ"
        ],
        "operationId": "matterVerify",
        "summary": "Публичный просмотр дела по ссылке для проверки",
        "description": "Данные только для чтения, доступные по публичной ссылке для проверки дела: сведения о деле, журнал цепочки хранения доказательств (чтобы можно было пересчитать цепочку хешей) и сведения об опечатывании с доказательством OpenTimestamps и токеном eIDAS. Содержимое снимков, ссылки на скачивание и журнал доступа не передаются. attested_by.organization содержит заданное владельцем название компании, которой принадлежит дело (дело компании), или null; у личного дела организация не указывается. custody_log[].actor_org содержит компанию, к которой принадлежал исполнитель на момент создания записи (сохраняется вместе с записью, вне хешируемых данных), только для дела компании; у записей, сделанных до того, как это начали сохранять, указано null. Частота запросов ограничена для каждого IP-адреса клиента (по умолчанию 30 запросов за 60 секунд).",
        "security": [],
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]{16,64}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Представление дела.",
            "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 для опечатываний, выполненных до 2026-10-01 без токена eIDAS."
                            },
                            "eidas_reason": {
                              "type": "string",
                              "enum": [
                                "token_refused",
                                "unavailable",
                                "test_key",
                                "account_limit"
                              ],
                              "description": "Только если eidas_status имеет значение not_obtained. test_key: опечатывание выполнил вызывающий в тестовом режиме, поэтому метка времени eIDAS не запрашивалась. account_limit: у аккаунта ещё нет оплаченного счёта, и он достиг лимита eIDAS (20 за сутки по UTC, 50 за месяц по UTC), поэтому метка времени eIDAS не запрашивалась."
                            },
                            "eidas_verification_status": {
                              "type": "string",
                              "nullable": true,
                              "description": "verified, если токен прошёл проверку ProofSnap при опечатывании."
                            },
                            "eidas_revocation_status": {
                              "type": "string",
                              "nullable": true,
                              "enum": [
                                "good",
                                "revoked_after_gen_time",
                                "unknown",
                                "not_checked",
                                null
                              ],
                              "description": "Статус сертификата службы меток времени, установленный при опечатывании (в реальном времени не запрашивается)."
                            },
                            "eidas_validation": {
                              "type": "object",
                              "nullable": true,
                              "additionalProperties": true,
                              "description": "Содержимое eidas_validation.json, записанное при опечатывании (цепочка службы меток времени, закреплённый корневой сертификат, ответ OCSP, CRL, запись проверки), чтобы токен в tsr_base64 можно было проверить офлайн. null для более старых опечатываний."
                            }
                          },
                          "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": "Всегда null."
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "NOT_FOUND: недопустимый, неизвестный или отозванный токен.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/captures/export-jobs": {
      "post": {
        "tags": [
          "Снимки"
        ],
        "operationId": "startCaptureExportJob",
        "summary": "Экспортировать все снимки одним скачиванием (асинхронное задание)",
        "description": "Запускает задание, которое упаковывает все ваши снимки в один набор для скачивания, разделённый на части размером не более примерно 2 ГБ. Каждая часть представляет собой обычный ZIP-архив: README.txt (на английском) плюс README.<language>.txt (тот же текст в переводе, если language не en; оба в UTF-8 с меткой порядка байтов и окончаниями строк CRLF; строка 1 каждого файла указывает на другой), captures.csv (UTF-8 с меткой порядка байтов, CRLF; все снимки экспорта со столбцами member (адрес электронной почты автора в экспорте компании, пусто в вашем собственном экспорте), capture_id, created_at (время получения запроса), captured_at (момент снимка; пусто для неудачного снимка), url, status (completed, failed или unavailable), export_part, file, ots_file (captures/<capture_id>.upgraded.ots, если эта часть содержит доказательство, иначе пусто), test_mode (true для снимка, сделанного тестовым ключом: с водяным знаком, не является доказательством) и note (почему пакет не включён, а также для каждой строки unavailable); в каждой части один и тот же список, а при расхождении между частями окончательным считается список из части с наибольшим номером) и captures/<capture_id>.zip, исходный опечатанный пакет доказательств байт в байт, плюс captures/<capture_id>.upgraded.ots, если сохранено обновлённое доказательство OpenTimestamps. Каждый вложенный пакет проверяется отдельно своим скриптом verification/verify.sh или verify.ps1. Пакет размером более 2 ГБ не включается (указывается как unavailable с примечанием; скачайте его отдельно через GET /captures/{capture_id}). Неудачные снимки указываются только в captures.csv; снимки в корзине и удалённые безвозвратно не экспортируются. Все записи каждой части датированы временем экспорта (UTC, также в поле расширенной метки времени). Опрашивайте GET /captures/export-jobs/{job_id}, пока status не станет ready. **Повторное использование:** если ваш последний экспорт готов, имеет тот же охват (и org) и language, до конца его окна скачивания остаётся не менее суток, ваши права не изменились и с момента его планирования ни один снимок не был добавлен, завершён, перемещён в корзину, восстановлен или удалён безвозвратно, запрос возвращает 200 с этим заданием (reused: true): ничего не пересобирается, и суточная единица не расходуется. Более новый готовый экспорт того же охвата заменяет предыдущий (предыдущее задание получает статус expired, expired_reason superseded). Кто может запустить экспорт: рабочий ключ API или сеанс в панели, на любом тарифе, включая sandbox, Archive only и ключи только для чтения (Website Archive). Тестовый ключ получает 403 FORBIDDEN (экспорт содержит рабочие снимки), а GET /captures/export-jobs/{job_id} возвращает тестовому ключу 404 EXPORT_JOB_NOT_FOUND для любого задания. Ваш собственный экспорт (scope own) содержит ваши личные снимки и снимки, сделанные вами для вашей текущей компании или компании, которой вы владеете, в том числе через ключ, пока эта компания является действующим архивом компании (снимки других участников компании в архиве доступны только через сеанс в панели). scope company (только для владельца компании, в том числе для компании, подписка которой закончилась, пока не закрылось её окно скачивания; для действующего архива компании только через сеанс в панели) экспортирует снимки всех участников, сделанные для компании, плюс ваши собственные, с адресом электронной почты автора в столбце member. Одно выполняющееся задание на аккаунт (409 EXPORT_JOB_RUNNING указывает его), 5 запущенных заданий на аккаунт за сутки по UTC (неудачное задание возвращает свою единицу, повторно использованное не расходует ни одной), а также поминутное ограничение частоты запросов вашего тарифа. Не более 20 000 самых новых снимков на задание (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": "При scope company: одна из компаний, владельцем которых вы являетесь (текущая или последняя), если вы владеете обеими."
                  },
                  "language": {
                    "type": "string",
                    "default": "en",
                    "description": "Язык перевода README: README.<language>.txt (полный перевод) рядом с английским README.txt в каждой части; en даёт только 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. Регистр, дефис и регион допускаются (\"PT-BR\" означает pt_BR); просто \"pt\" и \"zh\", а также любые другие значения дают 400 MALFORMED_REQUEST. Готовый экспорт используется повторно только для того же языка. Панель передаёт язык своего интерфейса."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Ваш готовый экспорт по-прежнему актуален и используется повторно: новое задание не создаётся, суточная единица не расходуется. Получите ссылки на него через 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": "Задание запущено.",
            "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: тело не является объектом JSON, или значение scope / org недопустимо.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: запрос выполнен с тестовым ключом или со scope company пользователем, который не является владельцем компании или у компании которого уже закрылось окно скачивания.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "EXPORT_JOB_RUNNING: задание экспорта аккаунта ещё выполняется; оно указано в details.job_id и 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": [
          "Снимки"
        ],
        "operationId": "getCaptureExportJob",
        "summary": "Статус и ссылки на скачивание задания экспорта снимков",
        "description": "Статус и ход выполнения задания; когда оно готово, по одной предподписанной ссылке на каждую часть. Ссылка действует до url_expires_at: не более 24 часов, никогда не дольше download_expires_at и никогда не дольше учётных данных, которыми она подписана (может быть меньше часа); после url_expires_at опросите снова, чтобы получить новые ссылки. download_expires_at наступает через 7 дней после полуночи по UTC, предшествовавшей запуску первой части задания (через 6-7 дней после готовности задания); после этого части удаляются. Задание досрочно теряет возможность скачивания (status expired, части удалены, expired_reason), если его заменил более новый экспорт того же охвата, содержащийся в нём снимок был безвозвратно удалён, данные его компании были удалены или ваши права больше его не охватывают (проверяется при каждом чтении готового задания). Задание, которое 30 минут не сообщает о ходе выполнения, этим вызовом завершается как failed, и всё, что оно записало, удаляется. Первый за каждый час ответ со ссылками записывается в журнал аудита (capture.export_job.links_issued). Задание видит только аккаунт, который его запустил; экспорт компании, хранящейся как архив компании, виден только через сеанс в панели.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cexp_[A-Za-z0-9]{16}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Задание.",
            "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, пока записываются план и части; ready со ссылками на скачивание; failed (запустите новое задание, дневная единица возвращена); expired, когда окно скачивания истекло или скачивание было отозвано (expired_reason; части удаляются)."
                    },
                    "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": "Язык README задания (README.<language>.txt помимо README.txt; en, если есть только 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, если у аккаунта больше 20 000 экспортируемых снимков: задание содержит 20 000 самых новых."
                    },
                    "counts": {
                      "type": "object",
                      "nullable": true,
                      "description": "Задаётся, когда задание готово.",
                      "properties": {
                        "captures": {
                          "type": "integer",
                          "description": "Строки файла captures.csv."
                        },
                        "packaged": {
                          "type": "integer",
                          "description": "Пакеты в частях."
                        },
                        "failed": {
                          "type": "integer",
                          "description": "Неудачные снимки (без пакета, указаны только в captures.csv)."
                        },
                        "unavailable": {
                          "type": "integer",
                          "description": "Пакеты, не вошедшие в экспорт: их не удалось прочитать во время выполнения экспорта или они больше 2 ГБ (какие именно, указано в captures.csv)."
                        }
                      }
                    },
                    "parts": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "required": [
                          "part",
                          "bytes",
                          "count",
                          "download_url"
                        ],
                        "properties": {
                          "part": {
                            "type": "integer",
                            "minimum": 1,
                            "example": 1,
                            "description": "Номер части, от 1 до количества частей."
                          },
                          "bytes": {
                            "type": "integer",
                            "example": 1834201112,
                            "description": "Размер ZIP-архива части в байтах."
                          },
                          "count": {
                            "type": "integer",
                            "example": 412,
                            "description": "Пакеты снимков в этой части."
                          },
                          "filename": {
                            "type": "string",
                            "example": "proofsnap-captures-2026-10-07T1430Z-part-1-of-3.zip",
                            "description": "Имя, под которым сохраняется скачанный файл (Content-Disposition): proofsnap-captures-<дата и время запуска задания по UTC>, с -part-N-of-M, если частей несколько."
                          },
                          "download_url": {
                            "type": "string",
                            "format": "uri",
                            "nullable": true,
                            "description": "Предподписанный GET для ZIP-архива части, действует до url_expires_at. null, если срок задания истёк."
                          }
                        }
                      }
                    },
                    "download_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Окончание окна скачивания: через 7 дней после полуночи UTC, предшествующей началу формирования первой части (после этого жизненный цикл хранилища удаляет части)."
                    },
                    "url_expires_at": {
                      "type": "string",
                      "format": "date-time",
                      "nullable": true,
                      "description": "Когда значения download_url в этом ответе перестают действовать: не более чем через 24 часа, никогда не позже download_expires_at и никогда не позже срока действия учётных данных подписи. После этого опросите снова, чтобы получить новые ссылки."
                    },
                    "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": "Задаётся, когда status равен expired: window_ended (срок download_expires_at истёк), superseded (появился более новый экспорт того же охвата), rights_changed (ваши права больше не распространяются на задание), capture_erased (снимок, входящий в задание, был удалён безвозвратно), company_deleted (данные его компании были удалены)."
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "EXPORT_JOB_NOT_FOUND: такого задания нет, задание принадлежит другому аккаунту, срок хранения его записи истёк (через 8 дней после завершения) или запрос был сделан тестовым ключом.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/webhook-subscriptions": {
      "get": {
        "tags": [
          "Вебхуки"
        ],
        "operationId": "listWebhookSubscriptions",
        "summary": "Список подписок на вебхуки",
        "description": "Подписки аккаунта на вебхуки, от самых новых, активные и отключённые, без секретов. Ключ API или сеанс в панели; можно использовать ключ только для чтения (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Подписки.",
            "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": [
          "Вебхуки"
        ],
        "operationId": "createWebhookSubscription",
        "summary": "Подписаться на события аккаунта",
        "description": "Подписывает эндпоинт https на события аккаунта: каждое подходящее событие аккаунта отправляется на него методом POST с подписью собственным секретом подписки (он возвращается один раз в этом ответе). Требуется подтверждённый адрес электронной почты (403 EMAIL_NOT_VERIFIED). Доступно и при просроченном платеже по подписке (ничего не оплачивается). Лимиты АКТИВНЫХ подписок на аккаунт: 25 на developer, startup, growth, custom, legal_solo и legal_seats, 10 на website_archive, 0 на archive_only и sandbox (409 WEBHOOK_SUBSCRIPTION_LIMIT). URL подчиняется правилам webhook_url (https, без учётных данных, без частных или зарезервированных адресов; проверяется повторно при каждой доставке). Поддерживается Idempotency-Key: повторный запрос с тем же ключом и телом в течение 24 часов получает первый ответ, включая секрет, вместо создания второй подписки. Ключ API или сеанс в панели; можно использовать и ключ только для чтения (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": "Простой текст, не более 200 символов."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Создано. `secret` показывается только здесь.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSubscriptionCreated"
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (events, filters, description, неизвестное поле), MISSING_FIELD (url) или 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 указывает на заблокированный адрес) или 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": [
          "Вебхуки"
        ],
        "operationId": "getWebhookSubscription",
        "summary": "Получить подписку на вебхуки",
        "description": "Одна подписка с состоянием доставки, без секрета. Ключ API или сеанс в панели; можно использовать ключ только для чтения (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Подписка.",
            "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: неизвестная, удалённая или принадлежащая другому аккаунту.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "patch": {
        "tags": [
          "Вебхуки"
        ],
        "operationId": "updateWebhookSubscription",
        "summary": "Изменить или повторно активировать подписку на вебхуки",
        "description": "Измените события, фильтры (null или {} сбрасывает их) или описание либо повторно активируйте отключённую подписку, передав status \"active\" (счётчики сбоев обнуляются; проверяется лимит активных подписок). URL изменить нельзя: создайте новую подписку. Требуется подтверждённый адрес электронной почты. Ключ API или сеанс в панели; можно использовать и ключ только для чтения (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": "Изменённая подписка.",
            "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: неизвестная, удалённая или принадлежащая другому аккаунту.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "WEBHOOK_SUBSCRIPTION_LIMIT (повторная активация сверх лимита).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "delete": {
        "tags": [
          "Вебхуки"
        ],
        "operationId": "deleteWebhookSubscription",
        "summary": "Удалить подписку на вебхуки",
        "description": "Удаляет подписку, её секрет и доставки в очереди. 404 для неизвестной или уже удалённой подписки (считайте её удалённой). Ключ API или сеанс в панели; ключу только для чтения (Website Archive) этот запрос тоже доступен.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "204": {
            "description": "Удалено.",
            "headers": {
              "X-Request-ID": {
                "$ref": "#/components/headers/XRequestId"
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "description": "WEBHOOK_SUBSCRIPTION_NOT_FOUND: неизвестная, удалённая или принадлежащая другому аккаунту.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/events": {
      "get": {
        "tags": [
          "Вебхуки"
        ],
        "operationId": "listEvents",
        "summary": "Журнал событий",
        "description": "Все события аккаунта за последние 30 дней, независимо от того, получила ли их какая-либо подписка, от новых к старым. Ключ API видит события своего окружения (рабочий ключ видит рабочие события, тестовый ключ видит тестовые); сеанс в панели видит рабочие события. Используйте этот метод, чтобы восполнить пропущенные события или опрашивать его вместо подписки. Ключ API или сеанс в панели; доступен и ключу только для чтения (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Только этот тип события.",
            "schema": {
              "$ref": "#/components/schemas/WebhookEventType"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "От 1 до 100, по умолчанию 25.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "Идентификатор события: страница начинается после него (передайте next_cursor). Идентификатор другого аккаунта или окружения либо старше 30 дней даёт ошибку 400.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Страница событий.",
            "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": "Передайте как starting_after для получения следующей страницы; null на последней странице."
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (type, limit или starting_after).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/whoami": {
      "get": {
        "tags": [
          "Вебхуки"
        ],
        "operationId": "whoami",
        "summary": "Кто я",
        "description": "Что представляют собой учётные данные: тариф, окружение, является ли ключ API ключом только для чтения, а также идентификатор, имя и префикс ключа (null для сеанса в панели). Интеграции используют эти данные, чтобы проверить подключение и дать ему название. Адрес электронной почты и идентификатор аккаунта не возвращаются. Ключ API или сеанс в панели; можно использовать ключ только для чтения (Website Archive).",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Учётные данные.",
            "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 для ключа API аккаунта Website Archive (он может читать данные и подписываться на события, но не может создавать снимки)."
                    },
                    "env": {
                      "type": "string",
                      "enum": [
                        "live",
                        "test"
                      ],
                      "description": "Окружение, в котором работают учётные данные."
                    },
                    "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": [
          "Снимки"
        ],
        "operationId": "listCaptureEvents",
        "summary": "Цепочка событий снимков (удаления и юридические удержания)",
        "description": "Защищённая от незаметного изменения запись аккаунта о том, что происходило с его снимками: перемещение в корзину, восстановление, безвозвратное удаление (erase), очистка корзины, удаление по сроку хранения, удаление вместе с аккаунтом или компанией, установка и снятие юридического удержания (снятие системой после окончания подписки имеет actor `system` и detail `subscription_ended`), результат S3 Object Lock и импорт пакета доказательств (type `import`, detail = тип пакета). От самых ранних. Цепочка хранится дольше, чем снимки и данные аккаунта: она содержит только идентификаторы и хеши манифестов, без содержимого снимков и без произвольного текста.\n\nКак проверить: (1) для каждого события вычислите SHA-256 от канонического JSON хешируемых полей (verification.hashed_fields; ключи отсортированы, без пробелов, отсутствующие значения как null) и сравните его с `hash`; (2) убедитесь, что каждый `prev_hash` равен `hash` предыдущего события (64 нуля для seq 1) и что в seq нет пропусков; (3) для привязки убедитесь, что `head_hash` равен `hash` события `to_seq`, запишите `ots_proof_base64` в файл (после декодирования base64) и выполните `ots verify -d <head_hash> anchor.ots` (клиент OpenTimestamps): это доказывает, что цепочка до этого события существовала не позднее блока Bitcoin. Привязки создаются раз в сутки для каждого аккаунта с событиями предыдущих суток.\n\nКлюч API или сеанс в панели, собственный аккаунт; `scope=company` даёт владельцу компании цепочку снимков компании (`org` выбирает одну, если вызывающий владеет несколькими; компанию, сохранённую как архив компании, только через сеанс в панели). Тестовый ключ получает 403 FORBIDDEN: цепочка охватывает рабочие снимки, и её нельзя отфильтровать, не нарушив её.",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "От 1 до 500, по умолчанию 100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 500,
              "default": 100
            }
          },
          {
            "name": "starting_after",
            "in": "query",
            "required": false,
            "description": "seq события: страница начинается после него (передайте 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: id компании.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Страница цепочки.",
            "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: недопустимое значение limit, starting_after или scope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "FORBIDDEN: запрос выполнен с тестовым ключом или со scope=company пользователем, который не владеет компанией.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      }
    },
    "/legal-holds": {
      "post": {
        "tags": [
          "Снимки"
        ],
        "operationId": "bulkLegalHold",
        "summary": "Массовая установка или снятие юридических удержаний",
        "description": "Устанавливает (enabled true) или снимает (false) юридическое удержание сразу для многих снимков. selector содержит ровно одно из полей: `capture_ids` (от 1 до 500 идентификаторов), `matter_id` (снимки дела, которое вызывающий может открыть), `host` (собственные снимки вызывающего с этим именем хоста) или `schedule_id` (собственные снимки вызывающего, сделанные по одному из его расписаний). Каждый снимок обрабатывается по правилам PATCH /captures/{capture_id} с действием legal_hold и получает собственный результат: applied, unchanged (уже в этом состоянии) или отказ с кодом ошибки (например, FORBIDDEN, CAPTURE_IN_TRASH, CAPTURE_PROCESSING, LEGAL_HOLD_OWNER_ONLY, LEGAL_HOLD_MATTER, LEGAL_HOLD_SOURCE для зачернения, исходный снимок которого всё ещё удерживается, CAPTURE_NOT_FOUND, LEGAL_HOLD_NOT_AVAILABLE для снимка в тестовом режиме). Зачернения, сделанные из каждого снимка, следуют его удержанию, в том числе для снимка с результатом unchanged; если это не удаётся завершить в рамках запроса, всё завершается в течение нескольких минут. Для установки удержаний нужен платный тариф: бесплатный sandbox получает 403 LEGAL_HOLD_NOT_AVAILABLE (details.reason \"plan\") на весь запрос; снятие работает на любом тарифе. Удержание, установленное здесь, имеет legal_hold_source \"bulk\"; снять его можно как массово, так и по одному. Не более 500 снимков на запрос: если у дела, хоста или расписания их больше, список обрезается (`truncated: true`, отправьте запрос ещё раз). Юридическое удержание S3 Object Lock для первых 25 изменённых снимков применяется в рамках запроса, для остальных в течение нескольких минут (`s3_legal_hold.status` pending). Стоимость: одна единица ограничения частоты запросов на каждые 10 снимков (не менее одной); запрос, которому нужно больше, чем допускает одно окно, возвращает 400 RATE_LIMIT_COST_TOO_HIGH. Ключ API или сеанс в панели; на Website Archive ключ API только для чтения (403 PLAN_API_READ_ONLY). Сеанс в панели или ключ с неподтверждённым адресом электронной почты получает 403 EMAIL_NOT_VERIFIED. Просроченный платёж по подписке не блокирует запрос.",
        "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": "Простой текст, сохраняемый с каждым установленным удержанием (но не со снятием); никогда не попадает в хешированные цепочки."
                  },
                  "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": "Результаты по каждому снимку.",
            "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": "Код ошибки отказа."
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (body, selector, более 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 (установка без платного тарифа, details.reason plan).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "MATTER_NOT_FOUND (matter_id, которое вызывающий не может открыть), 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": [
          "Импорт пакета"
        ],
        "operationId": "createImport",
        "summary": "Начать импорт пакета",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Создаёт импорт и начинает его загрузку. Передайте точный размер ZIP в байтах; ответ содержит по одному предподписанному URL для PUT на каждую часть размером 32 МиБ (последняя меньше), действительному 1 час (`upload.expires_at`; не дольше срока действия учётных данных подписи сервера). Отправьте байты каждой части методом PUT на её URL, сохраните заголовок `ETag` каждого ответа, затем вызовите POST /imports/{import_id}/complete в течение 24 часов. С `matter_id` принятый снимок добавляется в это дело: у вас должна быть возможность добавлять в него снимки прямо сейчас (открытое дело, которое вы можете открыть; импорт участника компании только в дело этой компании). Каждое создание учитывается в суточном лимите в 50 импортов.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "filename",
                  "size_bytes"
                ],
                "additionalProperties": false,
                "properties": {
                  "filename": {
                    "type": "string",
                    "maxLength": 200,
                    "description": "Отображаемое имя файла (простой текст: без управляющих, невидимых символов и символов направления текста)."
                  },
                  "size_bytes": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 2147483648,
                    "description": "Точный размер ZIP в байтах, не более 2 ГиБ."
                  },
                  "matter_id": {
                    "type": "string",
                    "description": "Необязательно: добавить принятый снимок в это дело."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Импорт создан; загрузите части.",
            "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": "Предподписанный URL S3 UploadPart: передайте через PUT ровно size_bytes байт (URL подписывает Content-Length) и прочитайте заголовок ответа ETag. Другие заголовки не нужны."
                              },
                              "size_bytes": {
                                "type": "integer",
                                "description": "Точная длина этой части в байтах: part_size для всех частей, кроме последней, остаток для последней (часть n начинается с (n - 1) x part_size)."
                              }
                            }
                          }
                        },
                        "expires_at": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "MALFORMED_REQUEST (body, filename, size_bytes, matter_id, неизвестное поле).",
            "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 (details.reason plan: только тарифы Legal; live_credentials_required: тестовый ключ, сеанс без привязанной оплаты или sandbox), EMAIL_NOT_VERIFIED, FORBIDDEN (импорт участника компании в дело другой компании).",
            "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 (уже выполняются 3 импорта; 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 больше 2 ГиБ (details.max_bytes).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED: исчерпан поминутный лимит или суточный лимит в 50 импортов (details.action import_create, details.daily_limit).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        }
      },
      "get": {
        "tags": [
          "Импорт пакета"
        ],
        "operationId": "listImports",
        "summary": "Список ваших импортов",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Ваши импорты, сначала новые. Завершённые импорты хранятся 30 дней (снимок принятого импорта остаётся). Загрузка, у которой истекли 24 часа, отображается со статусом 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 предыдущей страницы."
          }
        ],
        "responses": {
          "200": {
            "description": "Страница импортов.",
            "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": "Идентификатор импорта (imp_ и от 16 до 24 букв и цифр). Для некорректного идентификатора возвращается 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "get": {
        "tags": [
          "Импорт пакета"
        ],
        "operationId": "getImport",
        "summary": "Получить импорт",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Импорт и, когда он завершён, его результат. Опрашивайте его после POST /imports/{import_id}/complete; проверка обычно занимает секунды, пакет размером 2 ГиБ до нескольких минут. С ?upload=true для ещё не завершённой загрузки ответ дополнительно содержит upload: новые предподписанные URL (parts) для частей, которых ещё нет в хранилище, и уже полученные части (received: part_number, etag, size_bytes; null, если их не удалось перечислить, тогда заново подписываются все части), либо completed: true, если загрузка уже завершена (отправьте complete повторно). Используйте это, чтобы продолжить после обрыва сети, истечения URL части (они действуют не более 1 часа, окно загрузки 24 часа) или перезагрузки страницы. ?upload=true расходует одну единицу поминутного ограничения частоты запросов.",
        "responses": {
          "200": {
            "description": "Импорт.",
            "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 (также для импорта другого аккаунта).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "RATE_LIMITED (только с ?upload=true).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/AuthUnavailable"
          }
        },
        "parameters": [
          {
            "name": "upload",
            "in": "query",
            "required": false,
            "description": "true (или 1): добавить новые URL частей и уже полученные части текущей загрузки.",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "1"
              ]
            }
          }
        ]
      },
      "delete": {
        "tags": [
          "Импорт пакета"
        ],
        "operationId": "cancelImport",
        "summary": "Отменить загрузку",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Отменяет импорт, который ещё загружается: загрузка прерывается, части удаляются, а сам импорт удаляется (после этого GET возвращает 404).",
        "responses": {
          "200": {
            "description": "Отменено.",
            "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: импорт больше не находится на этапе загрузки (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": "Идентификатор импорта (imp_ и от 16 до 24 букв и цифр). Для некорректного идентификатора возвращается 404 IMPORT_NOT_FOUND.",
          "schema": {
            "type": "string",
            "pattern": "^imp_[A-Za-z0-9]{16,24}$"
          }
        }
      ],
      "post": {
        "tags": [
          "Импорт пакета"
        ],
        "operationId": "completeImport",
        "summary": "Завершить загрузку и проверить",
        "security": [
          {
            "ApiKeyAuth": []
          }
        ],
        "description": "Завершает загрузку и запускает проверку. parts необязателен: передайте все номера частей (от 1 до N, по порядку) с ETag, который вернул PUT каждой части, или не передавайте его, и сервер прочитает загруженные части из хранилища (удобно в браузере, который не может прочитать заголовок ETag). Проверяет, что сохранённый размер равен size_bytes. Опрашивайте GET /imports/{import_id}. Список частей, который хранилище отклоняет, или ещё не загруженная часть (details.missing_parts) дают 400, а загрузка остаётся открытой: загрузите недостающее (GET /imports/{import_id}?upload=true выдаёт новые URL) и отправьте запрос снова. Повторно отправлять complete после потерянного ответа безопасно: complete, который ещё выполняется, возвращает 409, прерванный перехватывается через 2 минуты, а уже завершённая загрузка переходит к проверке. Если размер отличается от size_bytes, загрузка отбрасывается (422 IMPORT_SIZE_MISMATCH; импорт отклоняется, создайте новый).",
        "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": "Заголовок ответа ETag на PUT этой части, в кавычках или без них."
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Проверка запущена.",
            "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 (части идут не по порядку или не совпадают с загруженными частями; или, без parts, часть ещё не загружена: 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: импорт уже не находится на этапе загрузки (details.status, например verifying или 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": "Передавайте ключ API в виде `Authorization: Bearer ps_live_...` (имя схемы не зависит от регистра). При отсутствующем или некорректном заголовке возвращается 401 MISSING_API_KEY. Для неизвестного, отозванного или просроченного ключа, а также для аккаунта без доступа к API возвращается 401 INVALID_API_KEY с `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. Код 403 зарезервирован для действительных учётных данных, которым не разрешено выполнять действие (FORBIDDEN, ограничения тарифа). Ключ `ps_live_` аккаунта без привязанного платёжного аккаунта (клиента Stripe) работает как тестовый ключ: снимки получают водяной знак и никогда не оплачиваются, а include_eidas отклоняется с ошибкой 403 EIDAS_REQUIRES_LIVE_KEY. Шлюз кеширует решение по ключу на срок до 60 секунд, поэтому отозванный ключ может продолжать работать до 60 секунд. Если учётные данные невозможно проверить из-за временного сбоя бэкенда, шлюз вместо этого возвращает 503 SERVICE_UNAVAILABLE; этот ответ не кешируется, поэтому повторите запрос с увеличивающейся задержкой. Заголовок `WWW-Authenticate` возвращается без изменений в ответе 401 от шлюза (проверка учётных данных); в ответе 401 от обработчика маршрута шлюз передаёт тот же заголовок под именем `x-amzn-Remapped-WWW-Authenticate`."
      }
    },
    "headers": {
      "XRequestId": {
        "description": "ID запроса; он пригодится и при обращении в поддержку. Передаётся в успешных ответах; в теле ошибки он содержится в поле request_id.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Число запросов, разрешённых в текущем 60-секундном окне. Устаревший заголовок, передаётся вместе с RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Число запросов, оставшихся в текущем окне. Устаревший заголовок, передаётся вместе с RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Время окончания текущего окна в формате ISO 8601. Устаревший заголовок, сохраняется наряду с RateLimit / RateLimit-Policy.",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "RetryAfter": {
        "description": "Время ожидания перед повторной попыткой, в секундах.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaLimit": {
        "description": "Снимки, включённые в тариф на месяц (рабочие ключи на тарифах с месячной квотой).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaRemaining": {
        "description": "Оставшиеся в этом месяце включённые снимки.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaUsed": {
        "description": "Завершённые снимки за этот месяц.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaCap": {
        "description": "Месячный максимум, действующий для вызывающего (значение тарифа по умолчанию, собственный лимит или лимит компании).",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgLimit": {
        "description": "Только для участников компании: месячный лимит на всю компанию, если владелец его установил.",
        "schema": {
          "type": "integer"
        }
      },
      "QuotaOrgUsed": {
        "description": "Только для участников компании: снимки, допущенные за этот месяц для всей компании.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxLimit": {
        "description": "Sandbox (сеанс в панели без тарифа): бессрочный лимит тестовых снимков с водяным знаком (50).",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxRemaining": {
        "description": "Sandbox: оставшийся лимит.",
        "schema": {
          "type": "integer"
        }
      },
      "SandboxUsed": {
        "description": "Sandbox: использованный лимит.",
        "schema": {
          "type": "integer"
        }
      },
      "IdempotentReplay": {
        "description": "\"true\", если этот ответ повторяет более ранний запрос с тем же Idempotency-Key.",
        "schema": {
          "type": "string",
          "enum": [
            "true"
          ]
        }
      },
      "WWWAuthenticate": {
        "description": "Запрос аутентификации (challenge) по RFC 6750, отправляемый с каждым ответом 401. Если 401 возвращает обработчик маршрута, а не шлюз, API Gateway передаёт этот заголовок под именем `x-amzn-Remapped-WWW-Authenticate`. Недействительный, отозванный или просроченный ключ API либо неверный или отозванный токен сеанса (INVALID_API_KEY): `Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. Отсутствующий или некорректный заголовок Authorization (MISSING_API_KEY): `Bearer realm=\"ProofSnap API\"`.",
        "schema": {
          "type": "string"
        },
        "example": "Bearer realm=\"ProofSnap API\", error=\"invalid_token\""
      },
      "RateLimitPolicy": {
        "description": "Политика ограничения частоты запросов в соответствии с черновиком IETF draft-ietf-httpapi-ratelimit-headers (в форме структурированного поля; формат основан на черновике и может меняться вместе с ним): `\"default\";q=<requests per window>;w=60`.",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";q=120;w=60"
      },
      "RateLimit": {
        "description": "Текущее состояние ограничения частоты запросов в соответствии с черновиком IETF draft-ietf-httpapi-ratelimit-headers (на основе черновика): `\"default\";r=<requests left>;t=<seconds until the window resets>`. Передаётся в ответах маршрутов с ограничением частоты запросов (одиночные и пакетные снимки, compare, redact, certify-hash) после того как запрос прошёл через ограничитель, а не только в ответе 429 (у запроса, отклонённого до ограничителя, например при аутентификации, этого заголовка нет).",
        "schema": {
          "type": "string"
        },
        "example": "\"default\";r=117;t=42"
      }
    },
    "parameters": {
      "CaptureId": {
        "name": "capture_id",
        "in": "path",
        "required": true,
        "description": "ID снимка, сертификата, сравнения, пакета с зачернёнными данными или импортированного пакета. ID начинаются с cap_, cert_, cmp_, red_ или imp_. Некорректный ID возвращает 404 CAPTURE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "cap_a1B2c3D4e5F6g7H8"
        }
      },
      "ScheduleId": {
        "name": "schedule_id",
        "in": "path",
        "required": true,
        "description": "Идентификатор расписания. Для расписания другого аккаунта возвращается 404 SCHEDULE_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "sch_a1B2c3D4e5F6g7H8"
        }
      },
      "MatterId": {
        "name": "matter_id",
        "in": "path",
        "required": true,
        "description": "ID дела. Для дела, участником которого вы не являетесь, возвращается 404 MATTER_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "mat_a1B2c3D4e5F6g7H8"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Необязательный ключ клиента, делающий повторную попытку безопасной. Используются только первые 255 символов, ключи действуют в рамках вашего аккаунта в течение 24 часов. Повторная попытка с тем же ключом и тем же телом возвращает исходное тело ответа с `Idempotent-Replay: true` (202 для одиночного снимка, пакетного запроса или сравнения, 201 для зачернения; `status` обновляется до текущего статуса снимка, сравнения или зачернения и каждого снимка пакетного запроса); новый снимок не создаётся и не оплачивается. Тот же ключ с другим телом возвращает 422 IDEMPOTENCY_KEY_REUSED. Повторная попытка, пока первый запрос ещё принимается, возвращает 409 IDEMPOTENCY_IN_PROGRESS с исходным ID в `error.details.capture_id` (batch_id для пакетного запроса).",
        "schema": {
          "type": "string",
          "maxLength": 255
        }
      },
      "SubscriptionId": {
        "name": "subscription_id",
        "in": "path",
        "required": true,
        "description": "Идентификатор подписки на вебхуки. Для подписки другого аккаунта возвращается 404 WEBHOOK_SUBSCRIPTION_NOT_FOUND.",
        "schema": {
          "type": "string",
          "example": "whs_a1B2c3D4e5F6g7H8"
        }
      }
    },
    "responses": {
      "RateLimited": {
        "description": "RATE_LIMITED: слишком много запросов в текущем 60-секундном окне. `error.details.retry_after_seconds` дублирует Retry-After. RateLimit сообщает r=0 и число секунд до сброса окна.",
        "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: непредвиденная ошибка сервера. Повторите запрос с экспоненциальной задержкой.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "AuthUnavailable": {
        "description": "SERVICE_UNAVAILABLE: шлюзу не удалось проверить учётные данные (временный сбой бэкенда). Ответ не кешируется: повторите запрос с увеличивающейся задержкой.",
        "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: запрошен include_eidas, но квалифицированные метки времени в этом развёртывании не настроены; повторите запрос без include_eidas. SERVICE_UNAVAILABLE: шлюз не смог проверить учётные данные (временный сбой бэкенда, результат не кешируется); повторите запрос с увеличивающейся задержкой.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "401, два кода. MISSING_API_KEY: заголовок Authorization отсутствует или имеет неверный формат (`WWW-Authenticate: Bearer realm=\"ProofSnap API\"`). INVALID_API_KEY: учётные данные переданы, но недействительны: неизвестный, отозванный или просроченный ключ API, аккаунт без доступа к API либо недействительный или отозванный токен сеанса (`WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`). Не повторяйте запрос с теми же учётными данными. 403 означает, что учётные данные действительны, но это действие им не разрешено (FORBIDDEN, ограничения тарифа).",
        "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": "Все ошибки имеют такой формат. Ответы с ошибкой содержат заголовок `X-Request-ID` с тем же значением, что и `request_id`.\n\nРазмер запроса: тело запроса может быть не больше 6 МБ (ограничение полезной нагрузки Lambda; сам API Gateway принимает не более 10 МБ). Более крупное тело отклоняется до того, как попадает в код API, поэтому такой ответ имеет другой формат.\n\n| HTTP | Код | Значение |\n| --- | --- | --- |\n| 400 | `MALFORMED_REQUEST` | Тело не является корректным JSON (сообщение \"Request body is not valid JSON\") или запрос некорректен: если `details.reason` указывает поле и причину, `message` повторяет эту причину, иначе он равен \"The request is invalid.\" |\n| 400 | `MISSING_FIELD` | Отсутствует обязательное поле. |\n| 400 | `INVALID_URL` | URL не является корректным http(s) URL, длиннее 2048 символов, или webhook_url не использует https. |\n| 400 | `INVALID_HASH` | certify-hash: хеш не состоит из 64 шестнадцатеричных символов. |\n| 400 | `INVALID_TIMESTAMP_TYPE` | options.timestamp_type не равен \"bitcoin\". |\n| 400 | `INVALID_VIEWPORT` | options.viewport не равен \"desktop\" или \"mobile\". |\n| 400 | `METADATA_TOO_LARGE` | options.metadata содержит больше 10 ключей, нестроковое значение или значение длиннее 500 символов. |\n| 400 | `INVALID_EVIDENCE_PACK_OPTIONS` | Некорректный options.evidence_pack. |\n| 400 | `INVALID_BATCH` | urls в пакетном запросе (batch) не является массивом из 2-50 корректных URL. |\n| 400 | `REDACTION_NO_OPS` | В зачернении нет операций, или ни одна из них ничего не нашла. |\n| 400 | `INVALID_CRON` | Не удалось разобрать cron_expr. |\n| 400 | `INTERVAL_TOO_SHORT` | Периодичность расписания ниже минимума тарифа. |\n| 400 | `RATE_LIMIT_COST_TOO_HIGH` | Запросу нужно больше единиц ограничения частоты запросов, чем допускает одно окно; `details.max_per_window`, `details.window_seconds`, `details.requested`. Разделите запрос. |\n| 400 | `WEBHOOK_REQUIRES_API_KEY` | `webhook_url` передан из сеанса в панели. Вебхуки подписываются секретом вебхука ключа API, поэтому для них нужен ключ API. |\n| 401 | `MISSING_API_KEY` | Заголовок Authorization отсутствует или имеет неверный формат. `WWW-Authenticate: Bearer realm=\"ProofSnap API\"`. |\n| 401 | `INVALID_API_KEY` | Ключ неизвестен, отозван или истёк, нет доступа к API, либо токен сеанса недействителен или отозван. `WWW-Authenticate: Bearer realm=\"ProofSnap API\", error=\"invalid_token\"`. |\n| 402 | `QUOTA_EXCEEDED` | Достигнут месячный максимум или лимит sandbox (также в `error` фонового заверения, отклонённого по этой причине). |\n| 402 | `PAYMENT_REQUIRED` | Платёж по рабочей подписке просрочен или не внесён (также в `error` снимка по расписанию, пропущенного по этой причине). |\n| 403 | `FORBIDDEN` | Ресурс принадлежит другому пользователю, или действие доступно только владельцу. Сообщение: \"You do not have access to this resource.\" (details.reason уточняет, где это полезно). |\n| 403 | `EMAIL_NOT_VERIFIED` | Сеанс в панели с неподтверждённым адресом электронной почты пытался создать что-то долговременное, платное или рассылающее приглашения (снимки, пакетные запросы, сравнения, зачернения, ключи API, секреты вебхуков, оформление оплаты, смену тарифа, изменение числа мест, certify-hash, расписания, приглашения в компанию, дела и любые их изменения, участников дел, привязку снимков к делам, публичные ссылки и их отзыв, опечатывание, экспорт, юридические удержания). Чтение остаётся доступным. Сообщение: \"Confirm your email address first. Use the link we sent you, or send a new one from the dashboard.\" Ключ API отклоняется так же, если адрес электронной почты его аккаунта не подтверждён. |\n| 403 | `EIDAS_REQUIRES_LIVE_KEY` | include_eidas передан с тестовым ключом (ключ ps_live_ аккаунта без привязанного платёжного аккаунта считается тестовым) или для сравнения либо зачернения тестового снимка (`details.reason`). Рабочий ключ или сеанс, который работает как тестовый, потому что платёжный аккаунт не привязан, получает `details.reason` \"no_billing_account\". |\n| 403 | `MATTERS_NOT_ENABLED` | Для дел нужен тариф Legal. |\n| 403 | `SCHEDULE_LIMIT_REACHED` | Достигнут лимит активных расписаний тарифа (или в тарифе нет расписаний). |\n| 403 | `ON_CHANGE_REQUIRES_PLAN` | Тариф не допускает мониторинг on_change. |\n| 403 | `PLAN_ARCHIVE_ONLY` | Тариф Archive only сохраняет ваши доказательства доступными для чтения, скачивания и проверки; на нём не создаются снимки, расписания, заверения, сравнения и зачернения. |\n| 403 | `PLAN_NOT_AVAILABLE` | Этот аккаунт не может выбрать тариф: Archive only покупается после окончания платной подписки API (пока доказательства ещё хранятся) или через смену тарифа с developer, startup, growth, website_archive или legal_solo; архив компании может выбрать только владелец компании, чья подписка закончилась, пока не закрылось её окно скачивания. Причина указана в `details.reason`. |\n| 403 | `PLAN_API_READ_ONLY` | Тариф Website Archive: ключ API только для чтения (список, скачивание, проверка, вебхуки). Создавайте снимки, расписания и заверения в панели или используйте тариф API. |\n| 403 | `LEGAL_HOLD_OWNER_ONLY` | Снять юридическое удержание со снимка компании может только владелец компании (установить его может участник компании). |\n| 403 | `LEGAL_HOLD_NOT_AVAILABLE` | Для установки юридического удержания нужны платный тариф (`details.reason` \"plan\": бесплатный sandbox или аккаунт, подписка которого закончилась) и рабочий снимок (`details.reason` \"test_capture\": тестовый снимок с водяным знаком). В снятии удержания по этой причине никогда не отказывают. |\n| 403 | `IMPORT_NOT_ALLOWED` | Для импорта пакета нужны тариф Legal (`details.reason` \"plan\") и рабочий ключ API или сеанс в панели с привязанной оплатой (`details.reason` \"live_credentials_required\"). |\n| 404 | `CAPTURE_NOT_FOUND` | Снимок не существует, удалён, или ID имеет неверный формат. |\n| 404 | `MATTER_NOT_FOUND` | Дело не существует, или вы не его участник. |\n| 404 | `IMPORT_NOT_FOUND` | Импорт не существует, принадлежит другому аккаунту, или ID имеет неверный формат. |\n| 404 | `WEBHOOK_SUBSCRIPTION_NOT_FOUND` | Подписка на вебхуки не существует, удалена или принадлежит другому аккаунту. |\n| 404 | `SCHEDULE_NOT_FOUND` | Расписание не существует или принадлежит другому аккаунту. |\n| 404 | `NOT_FOUND` | Другой ресурс не найден (формат хеша в реестре выпущенных пакетов, токен публичной ссылки, задание экспорта, участник). |\n| 404 | `EXPORT_JOB_NOT_FOUND` | Задание экспорта снимков не существует, принадлежит другому аккаунту, истекло или запрошено тестовым ключом. |\n| 405 | `METHOD_NOT_ALLOWED` | HTTP-метод не разрешён для этого маршрута. |\n| 409 | `CAPTURE_PROCESSING` | Снимок ещё обрабатывается, и его пока нельзя удалить (или изменить его юридическое удержание). |\n| 409 | `CAPTURE_LEGAL_HOLD` | На снимке установлено юридическое удержание, его нельзя удалить. |\n| 409 | `CAPTURE_IN_TRASH` | Юридическое удержание: снимок в корзине; сначала восстановите его. |\n| 409 | `CAPTURE_HOLD_GRACE` | Безвозвратное удаление в течение льготного периода после снятия юридического удержания со снимка; `details.until`. Перемещение в корзину разрешено. |\n| 409 | `LEGAL_HOLD_MATTER` | Удержание снимка установлено юридическим удержанием его дела, которое ещё действует: снимите его на уровне дела (`details.matter_id` для пользователя с доступом к делу). |\n| 409 | `LEGAL_HOLD_SOURCE` | Удержание зачернения или сравнения следует за снимком, из которого оно сделано (для сравнения за любым из сравниваемых снимков), а на нём ещё действует юридическое удержание (установленное на этом снимке или через его дело): пакет остаётся на удержании, пока это удержание не снимут (`details.source_capture_id`). |\n| 409 | `COMPANY_READ_ONLY` | Подписка компании закончилась (или она хранится как архив компании): её дела можно только открывать, экспортировать и отзывать их публичные ссылки, больше ничего, и из её снимков ничего нового не создаётся. |\n| 409 | `WEBHOOK_SUBSCRIPTION_LIMIT` | Достигнут лимит активных подписок на вебхуки тарифа (25 на тарифах API и Legal, 10 на Website Archive, 0 на Archive only и sandbox); `details.limit`, `details.active`. |\n| 409 | `WEBHOOK_SECRET_ROTATION_PENDING` | Предыдущий секрет вебхука ещё действует в течение 24-часового периода перекрытия; `details.previous_secret_expires_at`. |\n| 409 | `IDEMPOTENCY_IN_PROGRESS` | Первый запрос с этим Idempotency-Key ещё принимается. |\n| 409 | `COMPARE_NOT_READY` | Перед сравнением оба снимка должны быть завершены (completed). |\n| 409 | `REDACTION_NOT_READY` | Перед зачернением исходный снимок должен быть завершён (completed). |\n| 409 | `MATTER_SEALED` | Дело (или дело снимка) опечатано и не может изменяться. |\n| 409 | `MATTER_SEALING` | Идёт опечатывание дела (не более 60 с): в это время нельзя привязывать снимки, переименовывать, менять участников, перемещать в корзину связанный снимок или запускать повторное опечатывание. `Retry-After`. |\n| 409 | `MATTER_ARCHIVED` | Дело в архиве: нельзя добавлять снимки, переименовывать, добавлять участников, делиться ссылкой и экспортировать; сначала откройте его снова (PATCH status \"open\"). Убрать снимок или участника по-прежнему можно. |\n| 409 | `MATTER_CHANGED` | Журнал цепочки хранения доказательств изменился во время опечатывания; ничего не опечатано, повторите запрос. |\n| 409 | `MATTER_LINKED` | Удаление снимка: снимок входит в материалы дела, участником которого вызывающий больше не является. Сначала владелец дела должен убрать его из дела. |\n| 409 | `MATTER_HAS_TRASHED_CAPTURES` | Опечатывание дела: один из снимков дела в корзине. Восстановите его или уберите из дела, затем опечатайте снова. |\n| 409 | `MEMBER_HAS_INDIVIDUAL_SUBSCRIPTION` | Приглашение в компанию: собственная подписка API приглашённого ещё продлевается. Ему нужно отменить её, после чего владелец сможет пригласить его снова. |\n| 409 | `IMPORT_LIMIT` | Уже выполняются 3 импорта (загрузка или проверка); `details.limit`, `details.in_progress`. |\n| 409 | `IMPORT_STATE` | Импорт находится в состоянии, которое этого не допускает (завершение или отмена импорта, который уже не загружается); `details.status`. |\n| 409 | `EXPORT_JOB_RUNNING` | Экспорт ваших снимков ещё готовится; он указан в `details.job_id` и `details.poll_path`. Запустите новый после его завершения. |\n| 413 | `IMPORT_TOO_LARGE` | Пакет больше 2 ГиБ; `details.max_bytes`. |\n| 413 | `VERIFY_PACKAGE_TOO_LARGE` | POST /captures/{capture_id}/verify: сохранённый пакет больше 256 МиБ; проверьте его офлайн с помощью скриптов из пакета. `details.size_bytes`, `details.limit_bytes`. |\n| 422 | `IDEMPOTENCY_KEY_REUSED` | Idempotency-Key повторно использован с другим телом или в другом окружении ключа (live или test). |\n| 422 | `SSRF_BLOCKED` | URL указывает на частный, loopback, link-local или иной заблокированный адрес. |\n| 422 | `TARGET_UNREACHABLE` | У имени хоста URL нет записей DNS, или его не удалось разрешить. |\n| 422 | `REDACTION_SCREENSHOT_UNREADABLE` | Не удалось декодировать скриншот для зачернения областей. |\n| 422 | `REDACTION_LEAK_DETECTED` | Зачерняемый термин сохранился после зачернения за пределами URL снимка; ничего не выпущено. `details.files`, `details.rule_indexes`, `details.metadata_fields`, `details.in_capture_url`. |\n| 422 | `REDACTION_SOURCE_UNSUPPORTED` | Зачернять можно только снимки веб-страниц. `details.source` или `details.manifest_type`. |\n| 422 | `REDACTION_SOURCE_INTEGRITY_FAILED` | Файл исходного снимка не совпадает с SHA-256, указанным в его подписанном манифесте, или отсутствует; ничего не выпущено. `details.mismatched`, `details.missing`. |\n| 422 | `COMPARE_PROFILE_MISMATCH` | POST /compare: два снимка сделаны с разными профилями устройства или разными настройками снимка; `details.differing`, `details.before`, `details.after`. Чтобы всё равно сравнить, передайте allow_profile_mismatch: true (результат по тексту и изображению тогда не определён). |\n| 422 | `COMPARE_URL_MISMATCH` | POST /compare: снимки сделаны с разных страниц; `details.before`, `details.after` (url и final_url как origin и путь). Чтобы всё равно сравнить, передайте allow_url_mismatch: true. |\n| 422 | `RULE902_NOT_AVAILABLE_FOR_MISMATCH` | POST /compare: include_rule_902 для снимков, отрисованных по-разному (allow_profile_mismatch); `details.differing`. |\n| 422 | `COMPARE_SOURCE_INTEGRITY_FAILED` | Файл исходного снимка в POST /compare не совпадает с SHA-256, указанным в его подписанном манифесте, или отсутствует; ничего не сравнено и не списано. `details.capture_id`, `details.mismatched`, `details.missing`, `details.sources[]`. |\n| 422 | `REDACTION_PATTERN_TOO_SLOW` | Регулярное выражение text_redactions выполнялось на этой странице слишком долго (катастрофический бэктрекинг); ничего не выпущено и не списано. `details.rule_index`. Упростите шаблон. |\n| 422 | `IMPORT_SIZE_MISMATCH` | Размер загруженного пакета не совпадает с заявленным size_bytes; загрузка отброшена. `details.expected`, `details.actual`. |\n| 422 | `IMPORT_NOT_SUPPORTED` | Сравнение или зачернение импортированного пакета доказательств. |\n| 429 | `RATE_LIMITED` | Слишком много запросов в текущем окне, или достигнут суточный лимит на создание дел (200), опечатывание (20), экспорт (50), приглашения участников (50) или импорт пакетов (50, `import_create`) (`details.daily_limit`, `details.action`). `Retry-After` указывает, когда повторить запрос. |\n| 429 | `CONCURRENT_CAPTURE_LIMIT` | Одновременно выполняется слишком много снимков аккаунта (developer 10, startup 25, growth 100, legal_solo и legal_seats 25, custom 200, website_archive 25, sandbox 5). Пакетный запрос запускает столько URL, сколько укладывается в лимит, а остальные перечисляет в `not_admitted`; если свободных слотов нет, отклоняется весь запрос. `details.limit`, `details.in_flight`, `details.requested`, `Retry-After`. |\n| 429 | `TEST_MODE_DAILY_LIMIT` | Работа в тестовом режиме (тестовый ключ или сеанс без привязанной оплаты; с водяным знаком, не оплачивается) ограничена 200 единицами на аккаунт в сутки по UTC (пакетный запрос считает каждый свой URL). `details.used`, `details.limit`, `details.reset_at`, `Retry-After`. |\n| 429 | `IMPORT_DAILY_BYTES_LIMIT` | Импорт пакетов: достигнут объём импорта аккаунта за сутки по UTC (5 ГиБ заявленных размеров). `details.period`, `details.limit_bytes`, `details.used_bytes`, `details.requested_bytes`, `details.reset_at`. |\n| 429 | `IMPORT_MONTHLY_BYTES_LIMIT` | Импорт пакетов: достигнут объём импорта аккаунта за месяц по UTC (20 ГиБ). Те же details. |\n| 500 | `INTERNAL_ERROR` | Непредвиденная ошибка. Повторите запрос с увеличивающейся задержкой. |\n| 500 | `CAPTURE_FAILED` | Снимок не удалось запустить или (в `error` неудачного снимка) завершить. |\n| 500 | `CAPTURE_TIME_BUDGET_EXCEEDED` | В `error` неудачного снимка или при синхронном зачернении: выполнение исчерпало отведённое время, пакет не выпущен, оплата не списана. |\n| 500 | `CAPTURE_SCREENSHOT_TIMEOUT` | В `error` неудачного снимка: браузер не закончил отрисовку скриншота: все способы его получения (один кадр, более узкие полосы, собственный скриншот браузера) завершились по тайм-ауту, как и единственная повторная попытка в заново запущенном браузере, если на неё оставалось время. Ничего не списано; попробуйте снова. |\n| 500 | `CERTIFY_FAILED` | В `error` неудачного заверения: заверение не удалось завершить. |\n| 500 | `REDACTION_FAILED` | В `error` неудачного зачернения: зачернение не удалось завершить. Зачернение, отклонённое по известной причине, содержит вместо этого код этой причины (например, REDACTION_LEAK_DETECTED). |\n| 500 | `TIMESTAMP_SERVICE_UNAVAILABLE` | Опечатывание дела: привязка OpenTimestamps не удалась; ничего не изменилось, повторите запрос. |\n| 500 | `CONFIGURATION_ERROR` | Шаг на стороне сервера завершился ошибкой или не настроен (`details.reason`). |\n| 503 | `SERVICE_UNAVAILABLE` | Аутентификация временно недоступна (не удалось проверить учётные данные). Результат не кешируется; повторите запрос. |\n| 503 | `EIDAS_UNAVAILABLE` | Запрошен include_eidas, но метки времени eIDAS не настроены. |\n| 503 | `EIDAS_SEAL_RETRY` | Опечатывание дела: метка времени eIDAS не пришла вовремя; ничего не опечатано. Повторите запрос через `Retry-After`; повторный запрос использует уже выпущенную метку времени. |\n| 503 | `LIMIT_CHECK_UNAVAILABLE` | Опечатывание дела: не удалось проверить его суточный лимит; ничего не сделано. Повторите запрос. |\n| 504 | `UPSTREAM_TIMEOUT` | Синхронный шаг обработки (certify-hash, экспорт дела) не ответил вовремя. Он ещё может завершиться: опрашивайте то, что указано в `details` (`certify_id`, `poll_path`), или повторите запрос. |\n| 404 | `ORG_INVITE_NOT_FOUND` | Приглашение в компанию неизвестно, адресовано другому пользователю, больше не ожидает ответа, или его компания больше не работает (details.reason `company_inactive`). |\n| 409 | `ORG_INVITE_NO_SEATS` | Принятие приглашения в компанию: все места компании заняты; приглашение остаётся открытым. |\n| 409 | `ORG_INVITE_IN_OTHER_ORG` | Принятие приглашения в компанию: вызывающий занимает место в другой компании; приглашение остаётся открытым. |\n| 409 | `ORG_INVITE_HAS_SUBSCRIPTION` | Принятие приглашения в компанию: собственная подписка API вызывающего ещё продлевается; приглашение остаётся открытым. |\n| 409 | `ORG_OWNER_CANNOT_LEAVE` | Владелец компании попытался выйти из своей компании. |\n| 410 | `ORG_INVITE_EXPIRED` | Принятие приглашения в компанию старше 7 дней. |",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Машиночитаемый код ошибки (UPPER_SNAKE_CASE)."
              },
              "message": {
                "type": "string",
                "description": "Сообщение, понятное человеку."
              },
              "details": {
                "type": "object",
                "additionalProperties": true,
                "description": "Необязательный дополнительный контекст, например `reason`, `field` или `retry_after_seconds`."
              }
            },
            "required": [
              "code",
              "message"
            ]
          },
          "request_id": {
            "type": "string",
            "description": "ID запроса."
          }
        },
        "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": "Параметры снимка. Все поля необязательны.",
        "properties": {
          "scroll_full_page": {
            "type": "boolean",
            "description": "Снимать всю прокручиваемую страницу. При false снимается только область просмотра.",
            "default": true
          },
          "expand_collapsed": {
            "type": "boolean",
            "description": "Раскрывать свёрнутые разделы перед скриншотом: закрытые элементы <details>, кнопки аккордеонов и раскрывающихся блоков (aria-expanded с aria-controls) и текстовые элементы управления вида «see more» / «show replies». С защитой: ничего внутри навигации, заголовков страницы, меню или диалогов, никаких ссылок на другой URL, никакой отправки форм, никаких надписей, меняющих состояние (like, follow, buy, delete, ...), каждый элемент управления не более одного раза, а элемент управления, который пытается покинуть страницу, останавливает раскрытие, и исходный URL загружается снова. Результат записывается по каждому элементу управления в forensic_log.json (page_modifications.expand_collapsed: найдено, открыто, оставлено закрытым, причины пропуска, начатые запросы, высота страницы до и после) со сводкой в metadata.json (expand_collapsed: requested, opened_total, left_closed_total); evidence.pdf и сертификаты указывают только то, что действительно было открыто.",
            "default": true
          },
          "viewport": {
            "type": "string",
            "description": "desktop: 1920x1080 при коэффициенте плотности пикселей 1; mobile: 390x844 при коэффициенте плотности пикселей 3 с user agent мобильного Safari. Другие значения возвращают 400 INVALID_VIEWPORT.",
            "enum": [
              "desktop",
              "mobile"
            ],
            "default": "desktop"
          },
          "viewport_width": {
            "type": "number",
            "minimum": 320,
            "maximum": 3840,
            "description": "Пользовательская ширина области просмотра в CSS-пикселях. Передаётся только вместе с viewport_height, иначе 400 MALFORMED_REQUEST."
          },
          "viewport_height": {
            "type": "number",
            "minimum": 240,
            "maximum": 4320,
            "description": "Пользовательская высота области просмотра в CSS-пикселях. Передаётся только вместе с viewport_width."
          },
          "device_scale_factor": {
            "type": "number",
            "minimum": 1,
            "maximum": 3,
            "description": "Коэффициент плотности пикселей (device pixel ratio) скриншота."
          },
          "wait_for_selector": {
            "type": "string",
            "description": "Перед снимком ждать до 10 секунд появления этого CSS-селектора. Если он не появится, снимок продолжается без ошибки. Записывается в forensic_log.json page_modifications.",
            "maxLength": 200
          },
          "wait_ms": {
            "type": "number",
            "minimum": 0,
            "maximum": 15000,
            "description": "Дополнительное ожидание в миллисекундах после загрузки страницы."
          },
          "screenshot_format": {
            "type": "string",
            "description": "jpeg: в пакет входит screenshot.jpeg; png: в пакет входит screenshot.png без потерь. C2PA Content Credentials добавляются только к скриншотам JPEG.",
            "enum": [
              "jpeg",
              "png"
            ],
            "default": "jpeg"
          },
          "url_stamp": {
            "type": "boolean",
            "description": "Добавить над изображением страницы полосу заголовка с URL снимка, временем снимка по UTC (момент, когда браузер создал скриншот; для длинной страницы, снятой полосами, момент создания последней полосы) и идентификатором доказательства, с пометкой, что это программная аннотация. Полоса добавляется над изображением, поэтому ни один пиксель страницы не перекрывается. chain_of_custody.json записывает screenshot_url_header.raw_screenshot_sha256 вместе с raw_screenshot_source: для страницы, снятой целиком (browser_image, browser_png, browser_jpeg_joined, browser_jpeg_reencoded), это SHA-256 изображения страницы, созданного браузером, до добавления полосы; для длинной страницы, снятой полосами и склеенной (joined_bands, joined_bands_lossless, joined_bands_reencoded), это SHA-256 склеенного изображения в том виде, в каком ProofSnap записал его с полосой, до добавления C2PA Content Credentials. Если полосу добавить невозможно (с ней изображение превысило бы 65 535 px, допустимые для JPEG, её отрисовка не удалась или, только на резервном пути создания скриншота, изображение страницы больше 32 000 000 пикселей), скриншот передаётся без неё: metadata.json фиксирует это в screenshot.url_stamp_skipped, а chain_of_custody.json в screenshot_url_header (applied false с указанием причины).",
            "default": false
          },
          "network_log": {
            "type": "boolean",
            "description": "Включить network.har (HAR с заголовками запросов и ответов и таймингами, без тел) и network.original.har.",
            "default": true
          },
          "warc": {
            "type": "boolean",
            "description": "Включить capture.warc (ISO 28500) с HTTP-транзакциями загрузки страницы. Тела ответов читаются с ограничением по времени (не более 30 с и только пока это позволяет необязательная часть бюджета времени снимка, после времени, зарезервированного для eIDAS, C2PA и заключительного считывания часов); тело, обрезанное этим ограничением, сохраняется пустым с WARC-Truncated: time, ответ, до которого обработка не дошла, не включается, а warc_captured в chain_of_custody.json (и warc в forensic_log.json) записывает truncated, truncation_reason, response_pairs, records_total и счётчики.",
            "default": false
          },
          "external_archive": {
            "type": "boolean",
            "description": "Попросить сервис Internet Archive Save Page Now заархивировать URL (Internet Archive хранит эту копию публично). Результат классифицируется по тому, чей запрос создал архивную копию, и по его времени относительно запроса: created (создан для этого запроса), recent_existing (Internet Archive вернул более раннюю архивную копию), created_after_request (существует более новая архивная копия, но запрос не подтверждён), existing (существует только более старая архивная копия), pending, rate_limited, failed (Internet Archive отклонил запрос), unconfirmed (ни один ответ не подтвердил и не отклонил запрос, и архивная копия неизвестна), unavailable, skipped, skipped_sensitive или skipped_query. URL, которые, по-видимому, содержат учётные данные или параметры, имена параметров либо сегменты пути, похожие на токены, никогда не отправляются. URL со строкой запроса отправляется, только если этой опции явно задано значение true; со значением по умолчанию он пропускается (skipped_query). Результат записывается в forensic_log.json (external_archive, с snapshot_offset_seconds относительно снимка) и chain_of_custody.json и возвращается как external_archive в GET /captures/{capture_id}. Для закрытых URL задайте false. Запрос отправляется сразу после загрузки страницы, параллельно с остальной частью снимка. Сбой не приводит к ошибке снимка.",
            "default": true
          },
          "dismiss_consent": {
            "type": "boolean",
            "description": "Перед загрузкой страницы заранее установить cookie согласия «принять все» и попытаться закрыть баннеры согласия на cookie. Это изменяет страницу, поэтому по умолчанию отключено; выполненные действия записываются в forensic_log.json consent_dismissal.",
            "default": false
          },
          "dismiss_popups": {
            "type": "boolean",
            "description": "Закрывать всплывающие окна с подпиской на рассылку, акциями и подобные оверлеи их собственным элементом закрытия. Число закрытых окон и использованные элементы записываются в forensic_log.json page_modifications. Каждое закрытое всплывающее окно записывается в forensic_log.json page_modifications (popups_dismissed, popups_dismissed_controls); в evidence.pdf указано, сколько окон было закрыто.",
            "default": false
          },
          "dark_mode": {
            "type": "boolean",
            "description": "Отрисовать страницу в тёмной цветовой схеме. Меняет внешний вид страницы; записывается в page_modifications файла forensic_log.json. Указывается в evidence.pdf и в page_modifications файла forensic_log.json.",
            "default": false
          },
          "block_ads": {
            "type": "boolean",
            "description": "Блокировать запросы к известным рекламным и отслеживающим хостам. Исключает часть содержимого; записывается в forensic_log.json page_modifications.",
            "default": false
          },
          "block_resources": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "image",
                "font",
                "media",
                "stylesheet"
              ]
            },
            "description": "Типы ресурсов, которые нужно заблокировать. Исключает часть содержимого; записывается в page_modifications файла forensic_log.json."
          },
          "inject_css": {
            "type": "string",
            "description": "CSS, добавляемый на страницу перед снимком. Изменяет страницу; сохраняется дословно в forensic_log.json page_modifications. Всегда сохраняется дословно в forensic_log.json page_modifications (injected_css) с injected_css_status `applied` или `failed` и полем injected_css_error. В evidence.pdf указано, что был внедрён пользовательский CSS; сам код не печатается.",
            "maxLength": 10000
          },
          "inject_js": {
            "type": "string",
            "description": "JavaScript, выполняемый на странице перед снимком. Изменяет страницу. Всегда сохраняется дословно в forensic_log.json page_modifications (injected_js) вместе с injected_js_status и injected_js_error: `applied` (выполнен до конца), `invalid` (не скомпилирован, например из-за синтаксической ошибки, поэтому не выполнялся и страницу не изменил), `failed` (выбросил исключение во время выполнения, поэтому мог изменить страницу до остановки) или `timed_out` (снимок перестал его ждать через 5 секунд; скрипт мог продолжать работать и изменять страницу). evidence.pdf сообщает результат и ссылается на forensic_log.json, а сертификат происхождения указывает внедрённый код среди изменений страницы, если только он не `invalid`; ни один из них не выводит сам код.",
            "maxLength": 10000
          },
          "locale": {
            "type": "string",
            "description": "Языковой стандарт браузера для эмуляции, например de или de-DE (задаёт язык браузера и Accept-Language). Только эмуляция: прокси не используется, поэтому содержимое, зависящее от IP посетителя, по-прежнему отражает регион сервера снимков. Записывается в metadata.json geo.",
            "pattern": "^[a-z]{2}(-[A-Z]{2})?$"
          },
          "timezone": {
            "type": "string",
            "description": "Часовой пояс браузера для эмуляции (имя IANA, например Europe/Berlin). Только эмуляция."
          },
          "language": {
            "type": "string",
            "description": "Язык PDF-документов для чтения человеком. Одно из значений: 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. Регистр, дефис и регион допускаются (\"DE\", \"fr-FR\" и \"PT-BR\" принимаются); просто \"pt\" и \"zh\" отклоняются. Для неподдерживаемого значения возвращается 400 MALFORMED_REQUEST.",
            "default": "en",
            "example": "de"
          },
          "timestamp_type": {
            "type": "string",
            "description": "Принимается только \"bitcoin\" (привязка OpenTimestamps выполняется всегда). Любое другое значение возвращает 400 INVALID_TIMESTAMP_TYPE. Для квалифицированной метки времени используйте include_eidas.",
            "enum": [
              "bitcoin"
            ],
            "default": "bitcoin"
          },
          "include_eidas": {
            "type": "boolean",
            "description": "Дополнительно запросить квалифицированную метку времени eIDAS (RFC 3161) для SHA-256 файла manifest.json, она передаётся как manifest.json.tsr (и eidas_validation.json, если возвращаются данные проверки). Только для рабочих ключей: тестовый ключ возвращает 403 EIDAS_REQUIRES_LIVE_KEY. Если в развёртывании не настроена служба eIDAS, запрос отклоняется с ошибкой 503 EIDAS_UNAVAILABLE до какого-либо списания. Перед использованием токена ProofSnap проверяет его (подпись, содержащийся в нём хеш, цепочку сертификатов до корневого CA Disig QCA4, статус сертификата службы меток времени) и записывает результат проверки в eidas_validation.json (verification). Если запрос метки времени во время снимка не удался или токен не прошёл проверку, пакет передаётся без неё, только с привязкой OpenTimestamps, метка не оплачивается, а timestamps.eidas (строка снимка, GET /captures/{capture_id}, вебхук completed) содержит status not_obtained с причиной (token_refused, unavailable или time_budget). Начиная со скриншота, необязательные шаги снимка (тела WARC, ответы архива и WHOIS, подпись C2PA, второе считывание часов) оставляют время для метки и сокращаются первыми. Оплачивается за каждую метку.",
            "default": false
          },
          "include_rule_902": {
            "type": "boolean",
            "description": "Добавить rule_902_certification.pdf: неподписанный шаблон заверения по [FRE 902(13)/(14)](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-app-federalru-dup2-rule902.htm) и [28 U.S.C. 1746](https://www.govinfo.gov/content/pkg/USCODE-2023-title28/html/USCODE-2023-title28-partV-chap115-sec1746.htm), который человек заполняет и подписывает. Его SHA-256 указывается в manifest.json. Шаблон добавляется и тогда, когда evidence_pack.custodian имеет непустое значение. Если создать его не удалось, снимок завершается без него. Сейчас отключено: шаблон заверения временно выключен, поэтому поле принимается и игнорируется (rule_902_certification.pdf не создаётся и не указывается в пакете), а ответ содержит предупреждение RULE902_DISABLED.",
            "default": false
          },
          "case_reference": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Метка вашего дела. Повторяющиеся повторяющиеся пробелы заменяются одним. Подчиняется правилу простого текста (см. PlainText). Записывается в metadata.json, forensic_log.json и chain_of_custody.json и печатается в evidence.pdf.",
            "maxLength": 200
          },
          "case_id": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Идентификатор дела в вашей системе. Записывается так же, как case_reference; подчиняется правилу простого текста (см. PlainText).",
            "maxLength": 200
          },
          "case_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PlainText"
              }
            ],
            "description": "Произвольная заметка. Подчиняется правилу простого текста (см. PlainText). Записывается в metadata.json, forensic_log.json и chain_of_custody.json (legal_context.note).",
            "maxLength": 500
          },
          "webhook_url": {
            "type": "string",
            "description": "https URL, на который отправляются capture.completed или capture.failed. См. Webhooks. URL должен использовать https и не должен указывать на частный, loopback, link-local, carrier-grade NAT адрес или адрес облачных метаданных (те же правила, что и для URL снимка; иначе 422 SSRF_BLOCKED). Пустое значение или значение только из пробелов означает отсутствие вебхука. Нужен ключ API: при отправке из сеанса в панели запрос отклоняется с 400 WEBHOOK_REQUIRES_API_KEY, потому что вебхуки подписываются секретом вебхука ключа.",
            "format": "uri"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 500
            },
            "maxProperties": 10,
            "description": "До 10 строковых тегов (значения до 500 символов), возвращаются вместе со снимком. При нарушении возвращается 400 METADATA_TOO_LARGE."
          },
          "evidence_pack": {
            "type": "object",
            "properties": {
              "enabled": {
                "type": "boolean",
                "description": "false исключает страницу заверения из evidence.pdf.",
                "default": true
              },
              "jurisdiction": {
                "type": "string",
                "description": "Записывается в options файла forensic_log.json. Больше ни на что не влияет.",
                "enum": [
                  "US-Federal",
                  "EU-eIDAS"
                ],
                "default": "US-Federal"
              },
              "custodian": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Принимается; в шаблоне не печатается, поля декларанта в нём остаются пустыми для лица, которое его подписывает.",
                    "maxLength": 200
                  },
                  "law_firm": {
                    "type": "string",
                    "description": "Принимается; в шаблоне не печатается, поля декларанта в нём остаются пустыми для лица, которое его подписывает.",
                    "maxLength": 200
                  },
                  "role": {
                    "type": "string",
                    "description": "Принимается; в шаблоне не используется.",
                    "maxLength": 200
                  },
                  "case_caption": {
                    "type": "string",
                    "description": "Принимается; в шаблоне не используется.",
                    "maxLength": 200
                  }
                },
                "additionalProperties": false,
                "description": "Каждое значение подчиняется правилу простого текста (см. PlainText); иначе 400 INVALID_EVIDENCE_PACK_OPTIONS."
              }
            },
            "description": "Для недопустимых значений возвращается 400 INVALID_EVIDENCE_PACK_OPTIONS."
          }
        }
      },
      "CaptureRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Страница для снимка. Только http или https, не более 2048 символов. Частные адреса, loopback, link-local и адреса метаданных отклоняются с кодом 422 SSRF_BLOCKED, а для имени хоста без DNS-записей возвращается 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": "Путь статуса, включая этап /v1, например /v1/captures/cap_..."
          },
          "message": {
            "type": "string"
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "private_note": {
            "type": "string",
            "description": "Личная заметка в сохранённом (нормализованном) виде; присутствует, только если она была отправлена."
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "CaptureResult": {
        "type": "object",
        "description": "Снимок, сертификат, сравнение или зачернение. Набор полей зависит от status.",
        "properties": {
          "capture_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "processing",
              "completed",
              "failed"
            ]
          },
          "url": {
            "type": "string",
            "description": "URL снимка (null для сертификатов и сравнений).",
            "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 для сравнений, import для импортированного пакета доказательств.",
            "enum": [
              "comparison",
              "import"
            ]
          },
          "compared": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Только у сравнений: ID двух исходных снимков."
          },
          "diff_summary": {
            "$ref": "#/components/schemas/DiffSummary"
          },
          "estimated_completion_seconds": {
            "type": "integer",
            "description": "Только processing: приблизительная оценка, не менее 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": "Публичная причина сбоя (см. таблицу ошибок). CAPTURE_SCREENSHOT_TIMEOUT: браузер не завершил скриншот; CAPTURE_TIME_BUDGET_EXCEEDED: запуск израсходовал весь свой лимит времени; ни то, ни другое не оплачивается. Запись сравнения (cmp_) также может завершиться ошибкой COMPARE_SOURCE_INTEGRITY_FAILED (исходный пакет изменён после принятия запроса), COMPARE_PROFILE_MISMATCH или COMPARE_URL_MISMATCH (обнаруживаются только в опечатанных записях, которые сравнивает запуск) либо RULE902_NOT_AVAILABLE_FOR_MISMATCH; единица возвращается. Заверение хеша, выполняемое в фоне, завершается ошибкой QUOTA_EXCEEDED, если к моменту выполнения достигнут месячный максимум. CERTIFY_FAILED и REDACTION_FAILED: заверение хеша или зачернение не удалось завершить; повторите запрос."
              },
              "message": {
                "type": "string"
              }
            },
            "description": "Только failed."
          },
          "failed_at": {
            "type": "string",
            "description": "Только failed.",
            "format": "date-time"
          },
          "captured_at": {
            "type": "string",
            "description": "Когда был сделан снимок страницы (только для завершённых записей). Для зачернения это время снимка-источника (redacted_at указывает, когда было выполнено зачернение).",
            "format": "date-time"
          },
          "redacted_at": {
            "type": "string",
            "format": "date-time",
            "description": "Только для зачернений: когда было сделано зачернение. captured_at у зачернения совпадает с моментом съёмки исходного снимка."
          },
          "compared_at": {
            "type": "string",
            "nullable": true,
            "format": "date-time",
            "description": "Только для сравнений: когда выполнено сравнение. Его captured_at содержит то же значение (оставлено для старых клиентов); используйте compared_at."
          },
          "package_url": {
            "type": "string",
            "description": "Только completed: предподписанная ссылка на скачивание ZIP-архива пакета, действует до 24 часов (см. package_url_expires_at); чтобы получить новую ссылку, повторно запросите данные снимка.",
            "format": "uri"
          },
          "package_url_expires_at": {
            "type": "string",
            "description": "Только completed: момент, когда package_url перестаёт работать, не позднее 24 часов после этого ответа (ссылка, подписанная временными учётными данными сервера, может перестать работать раньше); чтобы получить новую ссылку, повторно запросите данные снимка.",
            "format": "date-time"
          },
          "verification_url": {
            "type": "string",
            "description": "Только completed: ссылка на публичный верификатор ProofSnap.",
            "format": "uri"
          },
          "manifest_hash": {
            "type": "string",
            "description": "Только completed: sha256:<hex> файла 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> подписанного скриншота.",
                "nullable": true
              }
            },
            "description": "Только completed."
          },
          "file_count": {
            "type": "integer",
            "description": "Только completed."
          },
          "size_bytes": {
            "type": "integer",
            "description": "Только 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": "Только completed, если снимок попал на экран согласия на использование cookie или на промежуточную страницу вместо самой страницы. Повторите запрос с dismiss_consent: true."
          },
          "external_archive": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ExternalArchive"
              }
            ],
            "description": "Только completed. Запрос к Internet Archive, сделанный с опцией external_archive: текущий результат (status), состояние, опечатанное в пакете (status_at_sealing), и ссылка на архивную копию. { requested: false }, если запрос не выполнялся."
          },
          "storage": {
            "$ref": "#/components/schemas/CaptureStorage"
          },
          "private_note": {
            "type": "string",
            "nullable": true,
            "description": "Ваша личная заметка (см. PrivateNote). Возвращается только автору снимка; никогда не входит в пакет доказательств."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "На снимок установлено юридическое удержание: пока true, снимок не удаляется (PATCH /captures/{capture_id} с action legal_hold). Снимается автоматически, а снимок удаляется, когда после окончания подписки прошло 30 дней без тарифа, сохраняющего данные."
          },
          "legal_hold_set_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Когда было установлено текущее юридическое удержание; null, если удержания нет."
          },
          "legal_hold_reason": {
            "type": "string",
            "nullable": true,
            "description": "Причина, сохранённая с текущим юридическим удержанием; null, если удержания или причины нет."
          },
          "legal_hold_set_by_email": {
            "type": "string",
            "nullable": true,
            "description": "Только для владельца компании, для снимка компании под удержанием: адрес электронной почты того, кто установил удержание."
          },
          "legal_hold_source": {
            "type": "string",
            "nullable": true,
            "description": "Источник текущего удержания: \"manual\" (PATCH /captures/{capture_id}), \"bulk\" (POST /legal-holds), \"matter:<matter_id>\" (юридическое удержание дела) или \"derived:<capture_id>\" (зачернение или сравнение, удерживаемое потому, что удерживается снимок, из которого оно сделано). null, если удержания нет."
          },
          "legal_hold_released_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Когда было снято последнее удержание (начало льготного периода после снятия); null, пока удержание действует или если оно никогда не снималось, а также после автоматического снятия по окончании подписки (без льготного периода)."
          },
          "deletion_blocked_until": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Окончание льготного периода после снятия удержания: до этого момента снимок нельзя удалить безвозвратно (безвозвратный DELETE возвращает 409 CAPTURE_HOLD_GRACE, очистка корзины и удаление по сроку хранения не выполняются); переместить его в корзину можно. Удаление аккаунта и компании после окончания подписки этого не ждёт. null, если льготный период не идёт (в том числе пока действует удержание: оно блокирует без срока окончания)."
          },
          "s3_legal_hold": {
            "type": "object",
            "nullable": true,
            "description": "Юридическое удержание S3 Object Lock для хранимых объектов пакета (ZIP, обновлённое доказательство OpenTimestamps и отдельно хранимая копия, все версии). status: on, off, pending (устанавливается), failed (сервер повторяет попытку), unsupported (в хранилище не настроен Object Lock; удержание записи снимка всё равно действует), none (пакет не хранится, например при неудавшемся снимке).",
            "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": "Зачернения и сравнения: true, если снимок, из которого они сделаны, находится под юридическим удержанием. Производный пакет следует удержанию своего источника: пока источник удерживается, зачернение или сравнение тоже удерживается (legal_hold_source derived:<capture_id>), и снять его удержание отдельно нельзя (409 LEGAL_HOLD_SOURCE). Для остальных снимков null."
          },
          "import": {
            "$ref": "#/components/schemas/CaptureImport"
          },
          "source": {
            "type": "string",
            "description": "Как был сделан снимок: api, schedule, certify-hash, compare, redact, import. GET /captures/{capture_id} возвращает это поле только для импортированного пакета (\"import\"); строки GET /captures содержат его всегда."
          }
        }
      },
      "Timestamps": {
        "type": "object",
        "properties": {
          "bitcoin_ots": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "description": "Состояние доказательства Bitcoin OpenTimestamps в manifest.json.ots. pending_confirmation: доказательство ожидает блока Bitcoin (обычно несколько часов; проверки начинаются примерно через 2 часа после завершения снимка и повторяются с растущими интервалами). verified: блок Bitcoin подтверждает хеш манифеста (block_height, block_time). not_present: в пакете нет manifest.json.ots (календари были недоступны в момент опечатывания пакета). unconfirmed: за 7 дней проверок подтверждение не найдено; доказательство в пакете по-прежнему можно обновить и проверить клиентом OpenTimestamps (ots upgrade, ots verify). package_missing: пакета доказательств больше нет в хранилище, поэтому доказательство больше невозможно проверить. manifest_mismatch: manifest.json в сохранённом пакете не совпадает с хешем манифеста, записанным при опечатывании; метка времени для него не подтверждена. Оба состояния окончательные. Сам пакет никогда не изменяется. not_checked: снимок был завершён до появления автоматических проверок подтверждения и ни разу не проверялся; его пакет может по-прежнему содержать manifest.json.ots, который клиент OpenTimestamps может обновить и проверить. Вебхук и ответ на создание отправляются при завершении, поэтому в них указано pending_confirmation (или not_present, если календари не ответили); текущее состояние читайте через GET /captures/{capture_id}. Пакет опечатывается с доказательством в состоянии ожидания: к этому моменту хеш манифеста уже отправлен в календари OpenTimestamps. После подтверждения в блоке Bitcoin {block_height} ({block_time} UTC) доказательство показывает, что manifest.json существовал не позднее этого времени; скачайте обновлённое доказательство (upgraded_proof_url) и поместите его рядом с manifest.json под именем manifest.json.upgraded.ots: тогда verify.sh и verify.ps1 проверят его автоматически.",
                "enum": [
                  "pending_confirmation",
                  "verified",
                  "not_present",
                  "unconfirmed",
                  "package_missing",
                  "manifest_mismatch",
                  "not_checked"
                ]
              },
              "block_height": {
                "type": "integer",
                "nullable": true,
                "description": "Высота блока Bitcoin, подтверждающего доказательство. Задаётся, когда status равно verified."
              },
              "block_time": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Время этого блока Bitcoin. Только в GET /captures/{capture_id}, задаётся, когда status равен verified."
              },
              "verified_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Когда ProofSnap подтвердил привязку. Только в GET /captures/{capture_id}, задаётся, когда status равен verified."
              },
              "upgraded_proof_url": {
                "type": "string",
                "format": "uri",
                "nullable": true,
                "description": "Только GET /captures/{capture_id}. После проверки привязки ProofSnap сохраняет доказательство OpenTimestamps, обновлённое по данным календарей (оно содержит подтверждение в блоке Bitcoin, поэтому проверяется офлайн без обращения к календарям). Это предподписанная ссылка на его загрузку, действительная до 24 часов, как package_url; чтобы получить новую ссылку, снова запросите данные снимка. null, если доказательства нет (ещё не проверено или будет сохранено позже при фоновом дозаполнении данных). Опечатанный пакет никогда не изменяется: сохраните файл рядом с manifest.json пакета как manifest.json.upgraded.ots. Пакеты, опечатанные начиная с 2026-09-29, проверяют его встроенными скриптами (verification/verify.sh и verify.ps1 используют его вместо manifest.json.ots); для более старого пакета выполните `ots verify manifest.json.upgraded.ots -f manifest.json`. Экспорт дела содержит его как captures/<capture_id>.upgraded.ots рядом с captures/<capture_id>.zip. Удаляется вместе с пакетом."
              }
            }
          },
          "eidas": {
            "$ref": "#/components/schemas/EidasTimestamp"
          }
        }
      },
      "DiffSummary": {
        "type": "object",
        "properties": {
          "text_insertions": {
            "type": "integer",
            "description": "Добавленные строки (то же значение, что text_lines_added; с 2026-10-14 текст считается в строках, см. text_unit). В сравнениях, сделанных раньше, считаются символы. Устарело (2026-10-14): поле сохранено для существующих клиентов и не удалено; используйте text_lines_added.",
            "deprecated": true
          },
          "text_deletions": {
            "type": "integer",
            "description": "Удалённые строки (то же значение, что text_lines_removed; строки с 2026-10-14, символы до этой даты). Устарело (2026-10-14): поле сохранено для существующих клиентов и не удалено; используйте text_lines_removed.",
            "deprecated": true
          },
          "text_unit": {
            "type": "string",
            "enum": [
              "line"
            ],
            "description": "Единица подсчёта текста: line (каждая строка domtextcontent.txt вместе с переводом строки). Отсутствует в сравнениях, сделанных до 2026-10-14, в которых считались символы."
          },
          "text_lines_added": {
            "type": "integer",
            "description": "Добавленные строки (строки текста After в добавленных фрагментах построчного сравнения)."
          },
          "text_lines_removed": {
            "type": "integer",
            "description": "Удалённые строки (строки текста Before в удалённых фрагментах построчного сравнения)."
          },
          "text_lines_before": {
            "type": "integer",
            "description": "Строки текста Before."
          },
          "text_lines_after": {
            "type": "integer",
            "description": "Строки текста After."
          },
          "text_changed_percent": {
            "type": "number",
            "description": "100 x (удалённые строки + добавленные строки) / (строки до + строки после), с одним знаком после запятой."
          },
          "text_diff_capped": {
            "type": "boolean",
            "description": "true, если построчный diff исчерпал свой бюджет работы и часть текстов была засчитана как заменённая целиком (завышает изменения, но никогда не скрывает их)."
          },
          "metadata_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "{ key, before, after, class } для каждого отличающегося поля metadata.json, только из этого списка: url, final_url, page_title, http_status, http_headers.content-type, http_headers.server, dns.connected_ip, resolved_ips (сравнивается как отсортированный список), tls.issuer, tls.valid_to и поля профиля устройства viewport_width, viewport_height, device_scale_factor, is_mobile и user_agent. class: content (url, final_url, page_title, http_status, http_headers.content-type: засчитывается как изменение), infrastructure (http_headers.server, dns.connected_ip, resolved_ips, tls.issuer, tls.valid_to: какой сервер отдал страницу, только для отчёта) или device_profile (как снимок отрисовал страницу, только для отчёта). У сравнения, сделанного до 2026-10-10, нет class."
          },
          "meta_content": {
            "type": "integer",
            "description": "Различающиеся поля метаданных класса content; они учитываются в changed."
          },
          "meta_infra": {
            "type": "integer",
            "description": "Различающиеся поля метаданных класса infrastructure (серверное ПО, IP-адрес подключения, IP-адреса из DNS-разрешения, издатель и срок действия сертификата TLS). Сеть доставки контента отвечает с меняющихся адресов, поэтому две загрузки неизменившейся страницы могут здесь различаться; приводятся для информации и никогда не учитываются в changed."
          },
          "meta_device_profile": {
            "type": "integer",
            "description": "Различающиеся поля профиля устройства (область просмотра, масштаб, эмуляция мобильного устройства, user agent); приводятся для информации и никогда не учитываются в changed."
          },
          "image_changes": {
            "type": "object",
            "description": "Сравнение изображений по перечню изображений, опечатанному каждым исходным снимком (image_inventory.json). status ran или not_run с reason source_without_inventory (исходный снимок сделан до того, как снимки стали записывать перечень изображений), source_inventory_failed или source_inventory_unreadable; ничего другого вместо него не используется. Полные результаты по каждому изображению находятся в image_changes.json пакета.",
            "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": "Почему изображения не сравнивались: source_without_inventory (исходный снимок сделан до того, как снимки стали записывать перечень изображений; новые снимки его записывают), source_inventory_failed (перечень изображений не удалось записать при создании того снимка) или source_inventory_unreadable; null, если сравнение было выполнено."
              },
              "images_changed": {
                "type": "integer",
                "nullable": true,
                "description": "Собственные изображения сайта (загружаемые с регистрируемого домена самой страницы, а с 2026-10-14 также с домена, который страница объявляет своим в og:image или иконке, или который отдаёт большую часть изображений страницы в обоих снимках: image_changes.json site_image_hosts), которые заменены, добавлены или удалены; это часть changed, относящаяся к изображениям; перемещённые изображения не учитываются. null, если сравнение не выполнялось. Устарело (2026-10-14): сохраняется для существующих клиентов и не удаляется; используйте 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, если перечень изображений источника достиг лимита записей; см. inventory_cutoff."
              },
              "site_images_changed": {
                "type": "integer",
                "description": "То же, что images_changed (количество)."
              },
              "other_images_changed": {
                "type": "integer",
                "description": "Изображения с других доменов и пиксели отслеживания, которые заменены, удалены или добавлены (никогда не учитываются)."
              },
              "site_image_hosts": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Регистрируемые домены, которые помимо домена страницы считаются собственными доменами сайта (site_image_hosts в image_changes.json)."
              },
              "inventory_cutoff": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "before_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Изображение, найденное только в Before, считается определимым (removed), если его индекс в перечне Before меньше этого значения; null: без ограничения (перечень After полный)."
                  },
                  "after_only_index_limit": {
                    "type": "integer",
                    "nullable": true,
                    "description": "Изображение, найденное только в After, считается определимым (added), если его индекс в перечне After меньше этого значения; null: без ограничения (перечень Before полный)."
                  }
                },
                "description": "Начиная со второго аудита от 2026-10-14 (алгоритм 3 в image_changes.json): объект, решение принимается по индексу в документе. Если ДРУГОЙ перечень изображений достиг лимита записей, изображение, найденное только с одной стороны, поддаётся определению, только если его индекс меньше числа записей другого перечня за вычетом запаса (большее из двух значений: 10 записей или 5 процентов этого числа); иначе оно не определено (undetermined_reason beyond_inventory_limit). null, если ни один перечень не усечён. Сравнения, сделанные раньше, содержат число (относительную позицию в документе по прежнему правилу)."
              }
            }
          },
          "changed": {
            "type": "boolean",
            "nullable": true,
            "description": "true, если отличается видимый текст, поле метаданных содержимого (meta_content) или собственное изображение сайта было заменено, удалено или добавлено (image_changes.images_changed). Поля инфраструктуры и профиля устройства, перемещённые изображения (те же байты по другому адресу), сторонние изображения и пиксели отслеживания попадают в отчёт, но не устанавливают это поле. null (с 2026-10-14), если ничего из учитываемого достоверно не изменилось, но результат по тексту или изображениям не определён (снимки отрисованы по-разному, allow_profile_mismatch). Пакет указывает это правило в comparison_method.json counted_as_change. Устарело (2026-10-14): сохранено для существующих клиентов и не удалено; используйте verdict (changed | unchanged | undetermined) и text_verdict, images_verdict, metadata_verdict по каждому виду изменений.",
            "deprecated": true
          },
          "text_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "undetermined, если два снимка были отрисованы с разными профилями устройства или настройками снимка (allow_profile_mismatch)."
          },
          "images_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined",
              "not_run"
            ]
          },
          "metadata_verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged"
            ],
            "description": "Из полей метаданных класса content (адрес, итоговый адрес, заголовок, HTTP-статус, тип содержимого)."
          },
          "verdict": {
            "type": "string",
            "enum": [
              "changed",
              "unchanged",
              "undetermined"
            ],
            "description": "Общий результат словами: changed, если changed равно true, undetermined, если changed равно null."
          },
          "undetermined_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "render_profile_mismatch",
              "capture_settings_mismatch",
              null
            ]
          },
          "text_changes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Только GET: до 60 изменённых фрагментов текста."
          },
          "change_regions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Только GET: до 60 изменений текста, локализованных на скриншотах (по text_layout.json исходных снимков), в пикселях скриншота. Каждое изменение содержит counterpart_y_px (его вертикальное положение на другом скриншоте, перенесённое от ближайшего неизменённого текстового блока) и report_region (номер места изменения, в котором оно показано в evidence.pdf, не более 10 мест, или null), как в change_regions.json."
          },
          "change_regions_info": {
            "type": "object",
            "nullable": true
          },
          "order_reversed": {
            "type": "boolean",
            "description": "Только GET: true, если снимок, переданный как capture_id_1 (До), сделан позже снимка, переданного как capture_id_2 (После), как order_reversed в compare.json. Сравнение сохраняет заданный порядок; отчёт и панель сообщают об этом."
          },
          "render_profile_mismatch": {
            "type": "boolean",
            "description": "Только GET: два снимка отрисованы с разными профилями устройства (compare.json)."
          },
          "capture_settings_mismatch": {
            "type": "boolean",
            "description": "Только GET: два снимка сделаны с разными настройками снимка (compare.json)."
          },
          "capture_settings_differing": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "setting": {
                  "type": "string"
                },
                "before": {},
                "after": {}
              }
            },
            "description": "Только GET: различающиеся настройки, { setting, before, after }."
          },
          "profile_mismatch_allowed": {
            "type": "boolean",
            "description": "Только GET: сравнение выполнено с allow_profile_mismatch для снимков, отрисованных по-разному. Устарело (2026-10-14): сохраняется для существующих клиентов и не удаляется; используйте render_profile_mismatch и capture_settings_mismatch.",
            "deprecated": true
          },
          "url_mismatch": {
            "type": "boolean",
            "description": "Только GET: у двух снимков нет общего адреса (выполнено с allow_url_mismatch)."
          }
        },
        "description": "Сводка сравнения."
      },
      "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": "Неопределённые пары, у которых обе стороны вернули одинаковый HTTP-статус ошибки (например, 404 в обоих снимках); входят в undetermined, учитываются в отчёте и не перечисляются."
          },
          "occurrences": {
            "type": "integer",
            "description": "Все результаты этой стороны: вхождения на странице."
          },
          "distinct": {
            "type": "object",
            "description": "Количество различных изображений среди результатов, всего и по категориям (изображение, показанное несколько раз, считается один раз): каждый результат соответствует SHA-256 своей стороны After или, если её нет, стороны Before, либо адресу этой стороны, если у неё нет 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": "Количество по категориям, считая вхождения на странице (изображение, показанное трижды, учитывается трижды): unchanged, replaced (тот же адрес, другие байты), moved (другой адрес, те же байты; сообщается, но не считается изменением), removed, added, undetermined (изображение, у которого на какой-либо стороне нет SHA-256; никогда не считается удалённым или добавленным); а также парные изображения, у которых изменился alt-текст или позиция среди изображений страницы, неопределённые пары с одинаковой ошибкой HTTP с обеих сторон и количество различных изображений."
      },
      "BatchRequest": {
        "type": "object",
        "required": [
          "urls"
        ],
        "properties": {
          "urls": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uri",
              "maxLength": 2048
            },
            "minItems": 2,
            "maxItems": 50,
            "description": "От 2 до 50 URL, каждый проверяется так же, как CaptureRequest.url. При приёме пакета имена хостов не проверяются через DNS; URL, который не проходит проверку DNS или адреса, позже завершается со статусом failed (и событием capture.failed)."
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "private_note": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PrivateNote"
              }
            ],
            "description": "Одна личная заметка, сохраняемая для каждого снимка пакетного запроса. Ваша личная заметка к снимку (например, «mobile capture»), не более 200 символов, простой текст (правило PlainText). Она хранится только в записи снимка: она НЕ входит в опечатанный пакет доказательств (её нет в ZIP, PDF, манифесте и ни в одном хеше) и не имеет доказательного значения, поэтому её можно позже изменить или очистить через PATCH /v1/captures/{capture_id}, не затрагивая доказательства. Читать её может только автор снимка. Чтобы не создавать заметку, не передавайте поле или отправьте null или пустую строку."
          }
        }
      },
      "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, если снимок не удалось запустить.",
                  "enum": [
                    "processing",
                    "failed"
                  ]
                }
              }
            }
          },
          "not_admitted": {
            "type": "array",
            "description": "Только если достигнут лимит одновременно выполняемых снимков аккаунта: URL, которые не были запущены, в порядке отправки. Они ничего не стоят (не расходуют ни ограничение частоты запросов, ни месячную единицу); отправьте их позже повторно. Отсутствует, если были запущены все URL.",
            "items": {
              "type": "object",
              "properties": {
                "url": {
                  "type": "string"
                },
                "code": {
                  "type": "string",
                  "description": "Почему URL не был запущен.",
                  "enum": [
                    "CONCURRENT_CAPTURE_LIMIT"
                  ]
                }
              }
            }
          },
          "in_flight_limit": {
            "type": "integer",
            "description": "Только вместе с not_admitted: количество снимков, которые аккаунт может выполнять одновременно (developer 10, startup 25, growth 100, legal_solo и 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: подпись действительна, и каждый перечисленный файл присутствует с записанным SHA-256. modified: подпись недействительна либо файл отсутствует или изменён. invalid: пакет не удалось проверить. null только для импортированного пакета, который в этот раз не проверялся повторно (`rechecked: false`, с `reason` и проверкой, записанной при импорте, в `stored_verification`).",
            "enum": [
              "match",
              "modified",
              "invalid",
              null
            ]
          },
          "reason": {
            "type": "string",
            "description": "Только invalid; для импортированного пакета с `rechecked: false`: почему он не был проверен повторно (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": "Только наличие manifest.json.ots; само доказательство здесь не проверяется.",
                    "enum": [
                      "present",
                      "not_present"
                    ]
                  }
                }
              },
              "eidas_disig": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "Наличие manifest.json.tsr. Сам токен проверяется в checks.eidas. missing: в manifest.json записано eidas_requested (пакет выпущен с токеном), но manifest.json.tsr в пакете отсутствует.",
                    "enum": [
                      "present",
                      "missing"
                    ]
                  }
                },
                "nullable": true,
                "description": "null, если manifest.json.tsr отсутствует и в manifest.json не записано eidas_requested."
              },
              "proofsnap_issuance": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "description": "issued_by_proofsnap: хеш манифеста есть в реестре выпущенных пакетов ProofSnap. unverified: его там нет (пакеты, выпущенные до появления реестра, в нём отсутствуют). unknown: поиск завершился ошибкой.",
                    "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, если manifest.json.tsr отсутствует и в manifest.json не записано eidas_requested. Токен метки времени eIDAS в том виде, в каком его по запросу проверил ProofSnap по сохранённому пакету. Это проверка, выполненная ProofSnap; в поле note указано, как проверить токен независимо.",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "verified",
                      "verified_revocation_unknown",
                      "failed",
                      "unavailable",
                      "missing"
                    ],
                    "description": "missing: в manifest.json записано eidas_requested: true, то есть пакет выпущен с квалифицированной меткой времени, но manifest.json.tsr в нём нет (тогда ответ содержит предупреждение EIDAS_TOKEN_MISSING). verified: все проверки пройдены, и статус сертификата службы меток времени, записанный при выдаче, известен (good или revoked после времени токена по причине, отличной от компрометации ключа). verified_revocation_unknown: все выполненные проверки пройдены, но статус сертификата службы меток времени не удалось установить по записанным данным (revocation.status unknown или not_checked). failed: как минимум одна проверка не пройдена, см. failures (тогда ответ содержит предупреждение EIDAS_TOKEN_FAILED). unavailable: вердикта по токену нет (reason: not configured, timeout, input_rejected, если верификатор отклонил входные данные, например слишком большие данные проверки, inconclusive); о самом токене из этого ничего не следует."
                  },
                  "reason": {
                    "type": "string",
                    "description": "Только unavailable: timeout, input_rejected, inconclusive или краткое пояснение."
                  },
                  "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": "Время в токене (TSTInfo genTime)."
                  },
                  "tsa_name": {
                    "type": "string",
                    "nullable": true,
                    "description": "Из сертификата подписавшего (общее имя и организация), никогда не значение по умолчанию."
                  },
                  "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": "Момент, на который проверялась цепочка: genTime токена, поэтому сертификат, истёкший после выдачи токена, не делает его недействительным."
                  },
                  "chain": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "subject": {
                          "type": "string"
                        },
                        "sha256": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "revocation": {
                    "type": "object",
                    "description": "Статус сертификата службы меток времени по данным, записанным при выдаче (basis recorded_at_issuance), проверенный по подписи; сертификат должен был быть действительным на момент genTime. В реальном времени не запрашивается.",
                    "properties": {
                      "status": {
                        "type": "string",
                        "enum": [
                          "good",
                          "revoked",
                          "revoked_after_gen_time",
                          "unknown",
                          "not_checked"
                        ],
                        "description": "revoked_after_gen_time: отозван после времени токена по причине, отличной от компрометации ключа (токен остаётся проверенным). not_checked: данные об отзыве не записывались и не проверялись."
                      },
                      "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": "Почему статус неизвестен."
                      }
                    }
                  },
                  "checks": {
                    "type": "object",
                    "description": "Результат каждой проверки: passed, failed, not_applicable (nonce: для сохранённого токена не записан), unknown (отзыв без пригодных данных) или not_checked (проверку остановил предыдущий сбой).",
                    "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": "Только input_rejected: что отклонил верификатор."
                  }
                }
              }
            },
            "description": "Отсутствует, если verdict равно invalid. verdict зависит только от manifest_signature и declared_files; eidas указывается рядом с ним, а при неудачной проверке также в warnings."
          },
          "warnings": {
            "type": "array",
            "description": "Присутствует, только если что-то вне вердикта требует внимания. EIDAS_TOKEN_FAILED: manifest.json.tsr не прошёл проверку в checks.eidas. EIDAS_TOKEN_MISSING: в manifest.json записано eidas_requested, но 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": "Страница для снимка, проверяется так же, как CaptureRequest.url.",
            "format": "uri",
            "maxLength": 2048
          },
          "mode": {
            "type": "string",
            "description": "interval: каждые interval_seconds. cron: по cron_expr. on_change: проверять страницу каждые interval_seconds и делать снимок, только если её текст изменился.",
            "enum": [
              "interval",
              "cron",
              "on_change"
            ]
          },
          "interval_seconds": {
            "type": "integer",
            "description": "Обязателен для interval и on_change. Не меньше минимума тарифа (300 на developer, startup, growth, custom, legal_solo и legal_seats; 86400 на website_archive), иначе 400 INTERVAL_TOO_SHORT.",
            "minimum": 300
          },
          "cron_expr": {
            "type": "string",
            "description": "Обязателен для cron (не более 200 символов). Для выражения, которое не удаётся разобрать, возвращается 400 INVALID_CRON; если два последовательных запуска ближе друг к другу, чем минимум тарифа, возвращается 400 INTERVAL_TOO_SHORT.",
            "maxLength": 200,
            "example": "0 9 * * 1-5"
          },
          "timezone": {
            "type": "string",
            "description": "Часовой пояс IANA, в котором вычисляется cron_expr. По умолчанию UTC.",
            "example": "Europe/Berlin"
          },
          "webhook_url": {
            "type": "string",
            "description": "https URL. Запуски interval и cron отправляют capture.completed или capture.failed; on_change отправляет monitor.change_detected. URL должен использовать https и не должен указывать на частный, loopback, link-local, carrier-grade NAT адрес или адрес облачных метаданных (те же правила, что и для URL снимка; иначе 422 SSRF_BLOCKED). Пустое значение или значение только из пробелов означает отсутствие вебхука. У расписания один webhook_url, на верхнем уровне: options.webhook_url не сохраняется (если webhook_url верхнего уровня не передан, options.webhook_url используется вместо него), поэтому расписание on_change никогда не отправляет дополнительно capture.completed. Нужен ключ API: при отправке из сеанса в панели запрос отклоняется с 400 WEBHOOK_REQUIRES_API_KEY, потому что вебхуки подписываются секретом вебхука ключа.",
            "format": "uri"
          },
          "js_render": {
            "type": "boolean",
            "description": "Только on_change: проверять страницу с полным рендерингом в браузере вместо обычной HTTP-загрузки (для страниц, которым нужен JavaScript).",
            "default": false
          },
          "options": {
            "$ref": "#/components/schemas/CaptureOptions"
          },
          "max_capture_age_seconds": {
            "type": "integer",
            "nullable": true,
            "minimum": 86400,
            "maximum": 34560000,
            "description": "Только on_change: дополнительно делать полный снимок (он отправляет capture.completed), если самый новый снимок расписания старше этого значения, даже если страница не изменилась. Для правил, требующих копию не реже чем каждые N дней, например 7776000 для 90 дней. От 1 до 400 дней и больше interval_seconds; null (по умолчанию) означает снимки только при изменении. PATCH с null удаляет параметр."
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "Только on_change (2026-10-14): какое обнаруженное изменение отправляет monitor.change_detected. any (по умолчанию, прежнее поведение): каждый снимок по изменению. text: только если сравнение с предыдущим снимком показывает, что видимый текст изменился (diff_summary.text_verdict changed); снимок по изменению, для которого сравнение показывает, что текст не изменился или это не удалось определить, ничего не отправляет (снимок всё равно делается, оплачивается и отправляет capture.completed подпискам). Без результата сравнения (нет базового снимка, сравнение не удалось) оповещение отправляется. 400 MALFORMED_REQUEST при другом режиме или значении. null для расписаний interval и 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": "Вебхук расписания (для более старого расписания, хранившего его в options.webhook_url, здесь показывается это значение, а options его никогда не содержит). Требуется ключ API: при отправке из сеанса в панели запрос отклоняется с 400 WEBHOOK_REQUIRES_API_KEY, потому что вебхуки подписываются секретом вебхука ключа."
          },
          "options": {
            "type": "object",
            "description": "Параметры снимка с применёнными значениями по умолчанию."
          },
          "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": "Причина автоматической приостановки расписания: max_failures (5 неудачных запусков подряд), no_billing, plan_downgrade (ограничения тарифа или тариф Archive only), invalid_cron или key_revoked. null, если расписание активно или приостановлено вами.",
            "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": "Самый новый снимок расписания со статусом COMPLETED (его текущая версия; last_capture_id является самым новым запущенным снимком)."
          },
          "current_captured_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "alert_on": {
            "type": "string",
            "enum": [
              "any",
              "text"
            ],
            "nullable": true,
            "description": "on_change: any или text (any для расписания, созданного до появления этой опции); null для interval и 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": "Записи в журнале цепочки хранения доказательств."
          },
          "custody_head_hash": {
            "type": "string",
            "description": "sha256:<hex> самой новой записи журнала цепочки хранения доказательств.",
            "nullable": true
          },
          "seal": {
            "type": "object",
            "nullable": true,
            "description": "null до опечатывания.",
            "allOf": [
              {
                "$ref": "#/components/schemas/MatterSeal"
              }
            ]
          },
          "member_uids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "created_by": {
            "type": "string",
            "description": "Владелец.",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          },
          "read_only": {
            "type": "boolean",
            "description": "Присутствует (true) у дела компании, подписка которой закончилась или которая сохранена как архив компании: доступны только открытие, экспорт и отзыв публичной ссылки."
          },
          "legal_hold": {
            "type": "boolean",
            "description": "На дело установлено юридическое удержание (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>: головной хеш цепочки хранения доказательств, который был опечатан и отправлен в OpenTimestamps."
          },
          "ots": {
            "type": "string",
            "description": "Доказательство 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": "Токен прошёл проверку ProofSnap при опечатывании (общий верификатор, те же проверки, что и для токена снимка)."
              },
              "revocation_status": {
                "type": "string",
                "enum": [
                  "good",
                  "revoked_after_gen_time",
                  "unknown",
                  "not_checked"
                ],
                "description": "Статус сертификата службы меток времени, установленный при опечатывании по полученному тогда ответу OCSP или CRL."
              },
              "validation": {
                "type": "object",
                "nullable": true,
                "description": "Содержимое eidas_validation.json, записанное при опечатывании, для офлайн-проверки токена: tsaCertChain, rootCaPem, ocspResponse, crlResponse (не включается и указывается в omitted, если слишком велик), ocspUrl, ocspFetchedAt, ocspStatus, crlUrl, verification. У опечатываний, сделанных до 2026-10-01, его нет.",
                "additionalProperties": true
              }
            },
            "nullable": true,
            "description": "Метка времени eIDAS для того же хеша, если в развёртывании настроен eIDAS, вызывающая сторона работает в рабочем режиме, а токен получен и прошёл проверку; иначе null (см. 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": "Отсутствует у опечатываний, выполненных до 2026-10-01. not_obtained: опечатывание содержит только привязку OpenTimestamps (eidas_reason token_refused, unavailable или test_key). Тайм-аут метки времени eIDAS никогда не приводит к опечатыванию: вместо этого запрос возвращает 503 EIDAS_SEAL_RETRY."
          },
          "eidas_reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "test_key",
              "account_limit"
            ],
            "description": "Только not_obtained. test_key: опечатано тестовой вызывающей стороной (тестовый ключ API или сеанс sandbox), поэтому метка времени eIDAS не запрашивалась. account_limit: у аккаунта ещё нет оплаченного счёта, и он достиг лимита eIDAS (20 за сутки по UTC, 50 за месяц по 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": "Например, matter.created, matter.updated, capture.added, capture.removed, capture.legal_hold_placed, capture.legal_hold_released, member.added, member.removed, package.imported (пакет доказательств, импортированный с matter_id; target равен идентификатору снимка)."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "prev_hash": {
            "type": "string"
          },
          "entry_hash": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "description": "Только package.imported: manifest_sha256, zip_sha256, package_kind, verdict, levels. Хранится вместе с записью, но НЕ охватывается entry_hash.",
            "additionalProperties": true
          }
        },
        "description": "Одно звено цепочки хешей. entry_hash охватывает seq, ts, actor, action, target и 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": "Например, capture.downloaded, export.generated, share.created, share.revoked."
          },
          "target": {
            "type": "string",
            "nullable": true
          },
          "source_ip": {
            "type": "string",
            "nullable": true,
            "description": "Только для владельца дела (у участников отсутствует)."
          },
          "user_agent": {
            "type": "string",
            "nullable": true,
            "description": "Только для владельца дела (у участников отсутствует)."
          }
        },
        "description": "Событие доступа. Не входит в цепочку хешей."
      },
      "CertifyResult": {
        "type": "object",
        "properties": {
          "certify_id": {
            "type": "string",
            "example": "cert_a1B2c3D4e5F6g7H8"
          },
          "status": {
            "type": "string",
            "enum": [
              "completed"
            ]
          },
          "hash_sha256": {
            "type": "string",
            "description": "Только для одиночного сертификата."
          },
          "combined": {
            "type": "boolean",
            "description": "Только сводный сертификат."
          },
          "file_count": {
            "type": "integer",
            "description": "Только сводный сертификат."
          },
          "files": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Отправленная вами метка.",
                  "nullable": true
                },
                "sha256": {
                  "type": "string"
                }
              }
            },
            "description": "Только сводный сертификат."
          },
          "label": {
            "type": "string",
            "description": "Ваша метка или \"N files\" для сводного сертификата.",
            "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 есть в пакете и ожидает блока Bitcoin. not_present: календари OpenTimestamps были недоступны, в пакете нет manifest.json.ots, и manifest.json его не перечисляет."
                  },
                  "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": "Ссылка на публичный сервис проверки ProofSnap.",
            "format": "uri"
          },
          "warnings": {
            "$ref": "#/components/schemas/Rule902DisabledWarnings"
          }
        }
      },
      "PlainText": {
        "type": "string",
        "description": "Правило простого текста для коротких полей свободного текста, которые печатаются в документах для суда или на публичных страницах (case_reference, case_id, case_note, значения evidence_pack.custodian, case_reference в certify-hash, хранитель при зачернении, декларация экспорта, названия компаний и дел). Значение нормализуется в NFC, повторяющиеся пробелы заменяются одним, а пробелы в начале и в конце удаляются, длина считается в символах (кодовых точках Unicode). Отклоняются: управляющие символы (C0, C1), встраивания, переопределения и изоляты bidi (от U+202A до U+202E, от U+2066 до U+2069), метки направления (U+200E, U+200F, U+061C), символы нулевой ширины и невидимые символы (U+200B, от U+2060 до U+2064, U+FEFF, U+00AD, U+034F, U+180E, от U+206A до U+206F, от U+FFF9 до U+FFFB), символы-заполнители хангыля (U+115F, U+1160, U+3164, U+FFA0), символы тегов (от U+E0000 до U+E007F), одиночные суррогаты и символы из области частного использования. Буквы любой письменности допускаются, включая слоги хангыля, арабское письмо, иврит, деванагари и CJK, а U+200C / U+200D (ZWNJ, ZWJ) разрешены, так как без них невозможно правильное написание в ряде письменностей."
      },
      "EidasTimestamp": {
        "type": "object",
        "description": "Результат eIDAS, если include_eidas был запрошен с рабочим ключом; отсутствует, если не запрашивался. confirmed: пакет содержит manifest.json.tsr (оплачивается, кроме случая, когда статус отзыва неизвестен). not_obtained: токен не включён в пакет и ничего не оплачено; пакет содержит только привязку OpenTimestamps (reason token_refused: токен получен, но не прошёл проверку при выдаче; unavailable: токен получить не удалось; time_budget: запрос не уложился в оставшееся время, поэтому он не был отправлен, был отклонён службой меток времени до обращения к удостоверяющему центру или был прерван, что происходит на синхронном маршруте зачернения или в конце долгого снимка; account_limit: у аккаунта ещё нет оплаченного счёта, и он достиг лимита eIDAS в 20 меток за сутки по UTC или 50 за месяц по UTC, считая вместе снимки, пакетные запросы, расписания, заверения, сравнения, зачернения и опечатывание дел, поэтому запрос не отправлялся; для аккаунтов с оплаченным счётом ограничений нет). Строки, завершённые до 2026-10-01, сообщают только confirmed.",
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "confirmed",
              "not_obtained"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "token_refused",
              "unavailable",
              "time_budget",
              "account_limit"
            ],
            "description": "Только not_obtained."
          },
          "serial": {
            "type": "string",
            "nullable": true,
            "description": "Только confirmed."
          },
          "issued_at": {
            "type": "string",
            "nullable": true,
            "description": "Только confirmed."
          },
          "revocation": {
            "type": "string",
            "enum": [
              "unknown"
            ],
            "description": "Только confirmed и только если статус сертификата службы меток времени не удалось установить при выдаче (все остальные проверки токен прошёл). Токен включается в пакет и не оплачивается."
          },
          "billed": {
            "type": "boolean",
            "enum": [
              false
            ],
            "description": "Присутствует (false) вместе с неизвестным статусом отзыва: этот токен не был оплачен."
          }
        }
      },
      "ExternalArchive": {
        "type": "object",
        "description": "Результат отправки снимка в Internet Archive (Wayback Machine). Internet Archive является публичным сторонним архивом.",
        "required": [
          "requested"
        ],
        "properties": {
          "requested": {
            "type": "boolean",
            "description": "false, если запрос к Internet Archive не выполнялся (external_archive: false, заверение или сравнение, снимок, сделанный до появления архивирования, или запрос не уложился в бюджет времени). Все остальные поля в этом случае отсутствуют."
          },
          "status": {
            "type": "string",
            "enum": [
              "created",
              "recent_existing",
              "created_after_request",
              "existing",
              "pending",
              "rate_limited",
              "failed",
              "unconfirmed",
              "unavailable",
              "skipped",
              "skipped_sensitive",
              "skipped_query"
            ],
            "description": "Текущий результат. Для задания Save Page Now, которое на момент опечатывания ещё ожидает выполнения, планировщик читает статус задания примерно через 5 и 30 минут после запроса, и это значение становится окончательным результатом (created, recent_existing, failed, rate_limited или unconfirmed, если ответ не пришёл в пределах окна проверки). created, recent_existing, created_after_request и existing содержат 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": "Состояние, опечатанное в пакете (его фиксируют metadata.json, forensic_log.json, chain_of_custody.json и evidence.pdf). Сам пакет никогда не меняется; совпадает со status, если только последующая проверка не установила окончательный результат ожидающего задания."
          },
          "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": "Публичная копия в Wayback Machine, всегда https://web.archive.org/web/{snapshot_timestamp}/{url}."
          },
          "snapshot_timestamp": {
            "type": "string",
            "nullable": true,
            "description": "Метка времени Wayback в формате YYYYMMDDhhmmss (UTC)."
          },
          "snapshot_offset_seconds": {
            "type": "integer",
            "nullable": true,
            "description": "Время архивной копии минус время снимка, в секундах (отрицательное значение: раньше снимка)."
          },
          "resolved_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true,
            "description": "Когда последующая проверка планировщика записала окончательный результат; null, если опечатанное состояние было окончательным."
          },
          "job_id": {
            "type": "string",
            "nullable": true,
            "description": "Идентификатор задания Save Page Now (для аутентифицированных запросов)."
          },
          "follow_up_pending": {
            "type": "boolean",
            "description": "true, пока планировщику ещё нужно прочитать статус задания."
          },
          "skip_reason": {
            "type": "string",
            "nullable": true,
            "description": "Почему URL не был отправлен, если status равен skipped, skipped_sensitive или skipped_query: credentials_in_url, sensitive_path, opaque_path_segment, sensitive_fragment, sensitive_parameter_name, opaque_parameter_value, query_string (или сам статус в более старых записях)."
          },
          "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 или pending, как прочитано из задания Save Page Now; null, если прочитать не удалось."
              },
              "snapshot_url": {
                "type": "string",
                "nullable": true,
                "description": "https://web.archive.org/web/<14 цифр>/<URL>, null, если ссылка отсутствует или недействительна."
              },
              "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": "Почему нет ответа по заданию: no_request_time (у строки нет времени запроса, от которого планируются чтения), capture_deleted или capture_failed (снимок был перемещён в корзину или завершился ошибкой, пока задание ожидало выполнения, поэтому дальше его не отслеживали; снимок, восстановленный из корзины в течение 30 минут после запроса, снова отслеживается). В остальных случаях null."
              }
            },
            "description": "Запись о последующей проверке (история) в виде фиксированной проекции: snapshot_url только если это действительный URL архивной копии Wayback для snapshot_timestamp; reason только если последующая проверка завершилась без ответа задания."
          }
        }
      },
      "CaptureStorage": {
        "type": "object",
        "description": "Срок хранения, дублирующая копия и (для снимков по расписанию) история версий.",
        "properties": {
          "retention": {
            "type": "object",
            "properties": {
              "years": {
                "type": "integer",
                "example": 10
              },
              "counted_from": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "Более позднее из значений created_at и последнего известного использования: последней проверки, обнаружившей, что страница не изменилась, или момента, когда этот снимок был заменён более новым снимком того же расписания."
              },
              "earliest_deletion_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true,
                "description": "counted_from плюс years, приблизительно: удаление выполняется при одном из последующих проходов планировщика, а последующие проверки могут сдвинуть эту дату. null при юридическом удержании."
              },
              "legal_hold": {
                "type": "boolean"
              }
            }
          },
          "duplicate_copy": {
            "type": "object",
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "none",
                  "pending",
                  "stored",
                  "not_copied"
                ],
                "description": "stored: вторая копия пакета (и его обновлённого доказательства OTS) хранится в отдельном бакете в другом регионе ЕС и удаляется вместе с оригиналом. pending: идёт копирование (минуты). none: тестовые пакеты, а также пакеты, созданные до появления дубликата, пока они не скопированы."
              },
              "copied_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              }
            }
          },
          "version": {
            "type": "object",
            "description": "Только снимки по расписанию.",
            "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": "Первая проверка (on_change), обнаружившая, что текст страницы не изменился с момента этого снимка."
              },
              "unchanged_last_at": {
                "type": "string",
                "format": "date-time",
                "nullable": true
              },
              "unchanged_checks": {
                "type": "integer",
                "description": "Записанные проверки, не выявившие изменений (не более одной в час)."
              }
            }
          }
        }
      },
      "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": "Событие из каталога (тег Webhooks)."
      },
      "WebhookSubscriptionFilters": {
        "type": "object",
        "description": "Не более одного из host и schedule_id. Тогда подписка получает только события строк с этим host (имя хоста URL снимка в нижнем регистре) или этого расписания. `{}`, если фильтр не нужен.",
        "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": "https-эндпоинт, на который события отправляются методом 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": "Окружение учётных данных, которыми она создана. Рабочая подписка получает события только рабочих записей, тестовая подписка только тестовых."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: создана с ключом API; отзыв этого ключа отключает подписку (disabled_reason key_revoked)."
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Неудачные доставки с момента последней успешной."
          },
          "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": "Класс ошибки последней неудачной доставки (timeout, connection_failed, blocked_address, redirect_not_followed, http_<status>)."
          },
          "disabled_reason": {
            "type": "string",
            "nullable": true,
            "enum": [
              "gone",
              "failing",
              "key_revoked",
              null
            ],
            "description": "gone: эндпоинт ответил кодом 410. failing: в течение 5 дней все доставки завершались ошибкой без единой успешной. key_revoked: ключ API, которым была создана подписка, отозван. PATCH status active снова активирует её."
          },
          "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": "https-эндпоинт, на который события отправляются методом 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": "Окружение учётных данных, которыми она создана. Рабочая подписка получает события только рабочих записей, тестовая подписка только тестовых."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "created_by": {
            "type": "string",
            "enum": [
              "key",
              "session"
            ],
            "description": "key: создана с ключом API; отзыв этого ключа отключает подписку (disabled_reason key_revoked)."
          },
          "secret": {
            "type": "string",
            "description": "whsec_, за которым следует стандартный base64. Показывается ОДИН раз: каждая доставка по этой подписке подписывается им (Standard Webhooks). Сохраните его сейчас: прочитать его повторно нельзя. Повтор с тем же Idempotency-Key в течение 24 часов возвращает то же тело, включая секрет."
          }
        },
        "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": "Тело события в том виде, в каком его получают подписки, без subscription_id и без предподписанных ссылок (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": "Ваша личная заметка к снимку (например, «mobile capture»), не более 200 символов, простой текст (правило PlainText). Она хранится только в записи снимка: она НЕ входит в опечатанный пакет доказательств (ни в ZIP, ни в PDF, ни в манифест, ни в какой-либо хеш) и не имеет доказательного значения, поэтому её можно позже изменить или удалить через PATCH /v1/captures/{capture_id}, не затрагивая доказательства. Читать её может только автор снимка. Чтобы не добавлять заметку, не передавайте поле или передайте null либо пустую строку.",
        "example": "mobile capture"
      },
      "CaptureEvent": {
        "type": "object",
        "description": "Одно событие цепочки событий снимков аккаунта. hash = SHA-256 (hex) канонического JSON хешируемых полей (v, chain, seq, type, capture_id, manifest_sha256, actor, access, detail, at, prev_hash; ключи отсортированы, без пробелов, отсутствующие значения как null). prev_hash: хеш предыдущего события, 64 нуля для seq 1.",
        "properties": {
          "v": {
            "type": "integer",
            "enum": [
              1
            ]
          },
          "chain": {
            "type": "string",
            "description": "Аккаунт: uid для личных снимков, org_<org_id> для снимков компании."
          },
          "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 файла manifest.json снимка, прочитанный до любого удаления."
          },
          "actor": {
            "type": "string",
            "description": "uid действующего аккаунта или system для фоновых проходов сервера."
          },
          "access": {
            "type": "string",
            "description": "author, company_owner, matter_owner или system."
          },
          "detail": {
            "type": "string",
            "nullable": true,
            "description": "legal_hold_on / legal_hold_off: источник удержания (manual, bulk, matter:<id>, derived:<capture_id>), а при снятии системой (actor system) код причины subscription_ended; s3_legal_hold: результат S3 (on, off, failed, unsupported, none); import: тип импортированного пакета. Произвольный текст не записывается."
          },
          "at": {
            "type": "string",
            "format": "date-time",
            "description": "Когда событие было записано в цепочку. Никогда не раньше at предыдущего события."
          },
          "prev_hash": {
            "type": "string"
          },
          "hash": {
            "type": "string"
          },
          "occurred_at": {
            "type": "string",
            "format": "date-time",
            "description": "Когда было зафиксировано само действие (транзакция изменения состояния). Не хешируется. Обычно совпадает с at с точностью до миллисекунд; позже только тогда, когда сервер повторял добавление в цепочку (действие записывается в той же транзакции, что и изменение, поэтому ни одно не теряется)."
          }
        }
      },
      "CaptureEventAnchor": {
        "type": "object",
        "description": "Ежедневная привязка цепочки через OpenTimestamps: head_hash содержит хеш события to_seq, который фиксирует все предыдущие события.",
        "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": "Доказательство OpenTimestamps (файл .ots, base64) для head_hash."
          },
          "ots_status": {
            "type": "string",
            "enum": [
              "pending",
              "bitcoin",
              "unconfirmed"
            ],
            "description": "pending: ots_proof_base64 содержит обязательство календарей (ots verify обращается к календарям); bitcoin: оно заменено доказательством, обновлённым до блока Bitcoin (bitcoin_block_height); unconfirmed: доказательство Bitcoin не было получено в течение 14 дней, сохранённое доказательство по-прежнему проверяется через календари."
          },
          "bitcoin_block_height": {
            "type": "integer",
            "nullable": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ImportSummary": {
        "type": "object",
        "description": "Три уровня проверенного пакета.",
        "properties": {
          "kind_label_key": {
            "type": "string",
            "description": "Ключ метки вида пакета, 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": "Когда реестр расширения записал пакет (provenance registered_by_extension_account)."
          },
          "issued_at": {
            "type": "string",
            "format": "date-time",
            "description": "Когда выпуск был записан в реестр выпущенных пакетов API (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": "Только uploading: завершите до этого времени (через 24 часа после создания)."
          },
          "finished_at": {
            "type": "string",
            "format": "date-time",
            "description": "Когда импорт достиг конечного статуса."
          },
          "capture_id": {
            "type": "string",
            "description": "Только accepted: новый снимок (с тем же id, что и у импорта)."
          },
          "duplicate_of": {
            "type": "string",
            "description": "Только duplicate: ваш снимок, который уже содержит этот манифест."
          },
          "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": "Тип пакета после его определения."
          },
          "summary": {
            "$ref": "#/components/schemas/ImportSummary"
          },
          "reasons": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "rejected и expired: коды причин, например 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. Только коды, никогда не имена файлов."
          },
          "zip_sha256": {
            "type": "string",
            "description": "SHA-256 загруженного ZIP-архива, после его вычисления."
          },
          "manifest_sha256": {
            "type": "string",
            "description": "SHA-256 файла manifest.json пакета (для accepted и duplicate)."
          },
          "matter_id": {
            "type": "string",
            "description": "Дело, указанное при создании импорта."
          },
          "matter_assignment": {
            "type": "string",
            "enum": [
              "assigned",
              "skipped"
            ],
            "description": "accepted с matter_id: был ли снимок добавлен в дело (не добавляется, если к тому моменту дело было опечатано, перемещено в архив или больше не было открыто для вас; импорт остаётся accepted)."
          },
          "upload": {
            "type": "object",
            "description": "Только с ?upload=true, пока status имеет значение uploading.",
            "properties": {
              "upload_id": {
                "type": "string"
              },
              "part_size": {
                "type": "integer"
              },
              "parts": {
                "type": "array",
                "description": "Предподписанные URL для PUT частей, которые ещё не получены; передайте через PUT ровно 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": "Загрузка уже завершена: отправьте complete повторно."
              }
            }
          }
        }
      },
      "ImportVerification": {
        "type": "object",
        "description": "Отчёт о проверке, записанный при импорте (без содержимого файлов и без произвольных текстовых подробностей).",
        "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": "Идентификатор, который содержит пакет (ps_..., FC-..., cap_..., cert_..., cmp_..., red_...)."
          },
          "manifest_sha256": {
            "type": "string"
          },
          "zip_sha256": {
            "type": "string"
          },
          "anchor_sha256": {
            "type": "string",
            "description": "Хеш, к которому относятся метки времени: хеш манифеста или, при заверении одного файла, хеш заверенного файла."
          },
          "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": "Только у снимка с 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 импортированного пакета: время, указанное в самом пакете, и никогда не время, зафиксированное 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": "Присутствует, только если запрос требовал шаблон заверения (include_rule_902: true или, для снимка, непустое evidence_pack.custodian), а шаблон отключён. Запрос принят, флаг проигнорирован: rule_902_certification.pdf не создаётся.",
        "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"
          ]
        }
      }
    }
  }
}
